File indexing completed on 2024-04-14 04:53:19

0001 /*
0002     This file is part of the KDE project.
0003 
0004     SPDX-FileCopyrightText: 2020 Stefano Crocco <stefano.crocco@alice.it>
0005 
0006     SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
0007 */
0008 
0009 #ifndef URLLLOADER_H
0010 #define URLLLOADER_H
0011 
0012 #include "konqopenurlrequest.h"
0013 #include "browseropenorsavequestion.h"
0014 
0015 #include <QObject>
0016 #include <QUrl>
0017 #include <QPointer>
0018 
0019 #include <KService>
0020 #include <KJob>
0021 
0022 namespace KParts {
0023     class ReadOnlyPart;
0024 };
0025 
0026 class KJob;
0027 namespace KIO {
0028     class OpenUrlJob;
0029     class ApplicationLauncherJob;
0030     class MimeTypeFinderJob;
0031 }
0032 namespace KonqInterfaces {
0033     class DownloaderExtension;
0034     class DownloaderJob;
0035 }
0036 class KonqMainWindow;
0037 class KonqView;
0038 
0039 
0040 /**
0041  * @brief Class which takes care of finding out what to do with an URL and carries out the chosen action
0042  *
0043  * Depending on whether the mimetype of the URL is already known and on whether the URL is a local or remote
0044  * file, this class can work in a synchronous or asynchronous way. This should be mostly transparent to the user.
0045  *
0046  * This class is meant to be used in the following way:
0047  * - create an instance, passing it the known information about the URL to load
0048  * - connect to the finished() signal to be notified when the URL has been loaded
0049  * - call start(): this will attempt to determine the synchronously determine mimetype and, if successful, will
0050  * decide what to do with it
0051  * - call viewToUse() to find out where the URL should be opened. If needed, create a new view and call setView()
0052  * passing the new view
0053  * - call goOn(): this will asynchronously determine the mimetype and the action to carry out, if not already done,
0054  * and perform the action itself.
0055  *
0056  * @internal
0057  * For remote files, what happens after goOn depends on whether the metadata associated with the request to open the
0058  * URL (`m_request.args.metaData()`) contains the key DownloaderInterface::requestDownloadByPartKey():
0059  * - if the key doesn't exist, this class will determine the mimetype of the URL (if needed), then embed, open or save it
0060  *   depending on the mimetype and the user's previous choices
0061  * - if the key exists, (after determining the mimetype of the URL, if not already known), this class will check whether
0062  *   the requesting part implements the DownloaderInterface. If so, it'll let the job returned by DownloaderInterface::downloadJob()
0063  *   to download the URL, then it will update #m_url so that it contains the path of the downloaded file. It then proceeds as
0064  *   in the above case. If the file should be saved, it'll simply moved from the download location to the location chosen by the user
0065  */
0066 class UrlLoader : public QObject
0067 {
0068     Q_OBJECT
0069 
0070 public:
0071     /**
0072      * Constructor
0073      *
0074      * @param mainWindow the KonqMainWindow which asked to load the URL
0075      * @param view the view which asked to open the URL. It can be `nullptr`
0076      * @param url the URL to load
0077      * @param mimeType the mimetype of the URL or an empty string if not known
0078      * @param req the object containing information about the URL loading request
0079      * @param trustedSource whether the source of the URL is trusted
0080      * @param forceOpen tells never to embed the URL
0081      */
0082     UrlLoader(KonqMainWindow* mainWindow, KonqView* view, const QUrl& url, const QString& mimeType, const KonqOpenURLRequest& req, bool trustedSource, bool dontEmbed = false);
0083     ~UrlLoader();
0084 
0085 
0086     /** @brief Enum describing the possible actions to be taken*/
0087     enum class OpenUrlAction{
0088         UnknwonAction, /**< The action hasn't been decided yet */
0089         DoNothing, /**< No action should be taken */
0090         Save, /**< Save the URL */
0091         Embed, /**< Display the URL in an embedded viewer */
0092         Open, /**< Display the URL in a separate viewer*/
0093         Execute /**< Execute the URL */
0094     };
0095 
0096     /** @brief Enum describing the view to use to embed an URL*/
0097     enum class ViewToUse{
0098         View, /**< Use the view passed as argument to the constructor */
0099         CurrentView, /**< Use the current view */
0100         NewTab /**< Create a new tab and use its view */
0101     };
0102 
0103     /**
0104      * @brief Determines what to do with the URL if its mimetype can be determined without using an `OpenUrlJob`.
0105      *
0106      * When the mimetype can be determined without using an `OpenUrlJob`, this function calls decideAction() to
0107      * determine what should be done with the URL. In this case, subsequent calls to isReady() will return `true`.
0108      * If the mimetype can't be determined without using an `OpenUrlJob`, calls to isReady() will return `false`,
0109      * because `OpenUrlJob` works asynchronously.
0110      *
0111      * The mimetype can be determined without using an `OpenUrlJob` in the following situations:
0112      * - a mimetype different from `application/octet-stream` is passed to the constructor
0113      * - the URL is a local file
0114      * - the URL scheme is `http` and the URL hasn't yet been processed by the default HTML engine (in this case,
0115      * a fake `text/html` mimetype will be used and the HTML engine will take care of determining the mimetype)
0116      *
0117      * @note This function *doesn't* create or start the `OpenUrlJob`, even if it will be needed.
0118      */
0119     void start();
0120 
0121     /**
0122      * @brief Performs the required action on the URL, using an `OpenUrlJob` to determine its mimetype if needed.
0123      *
0124      * If start() had been able to determine the action to carry out, this function simply calls performAction()
0125      * to perform the chosen action. In all other cases (that is, if the mimetype is still unknown), it launches
0126      * an `OpenUrlJob` to determine the mimetype. When the job has determined the mimetype, this function will
0127      * call decideAction() to decide what to do with the URL and then call performAction() to carry out the chosen
0128      * action.
0129      */
0130     void goOn();
0131 
0132     /**
0133      * @brief Carries out the requested action
0134      */
0135     void performAction();
0136 
0137     void abort();
0138 
0139     QString mimeType() const;
0140     bool isReady() const {return m_ready;}
0141     ViewToUse viewToUse() const;
0142     QUrl url() const {return m_url;}
0143     KonqOpenURLRequest request() const {return m_request;}
0144     KonqView* view() const {return m_view;}
0145     void setView(KonqView *view);
0146     bool isAsync() const {return m_isAsync;}
0147     void setOldLocationBarUrl(const QString &old);
0148     // QString oldLocationBarUrl() const {return m_oldLocationBarUrl;}
0149     bool hasError() const {return m_jobErrorCode;}
0150     void setNewTab(bool newTab);
0151 
0152     // /**
0153     //  * @brief whether a given mimetype refers to an executable program instead of a data file
0154     //  * @param mimeTypeName the mimetype to test
0155     //  * @return `true` if @p mimeTypeName refers to an executable and `false` otherwise
0156     //  */
0157     // static bool isExecutable(const QString &mimeTypeName);
0158     QString suggestedFileName() const {return m_request.suggestedFileName;}
0159 
0160     /**
0161      * @brief The @c ID of the part to use to open a local file
0162      *
0163      * @param path the file path
0164      * @return the plugin id for the preferred part for @p file (as returned by @c KPluginMetaData::pluginId() or
0165      * an empty string if no part could be found
0166      */
0167     static QString partForLocalFile(const QString &path);
0168 
0169 signals:
0170     void finished(UrlLoader *self);
0171 
0172 private slots:
0173 
0174     void mimetypeDeterminedByJob();
0175     void jobFinished(KJob* job);
0176     void done(KJob *job=nullptr);
0177 
0178     /**
0179      * @brief Slot called when a part which has asked to download itself the URL has finished doing so
0180      *
0181      * @param job the DownloaderJob used by the part to download the URL
0182      */
0183     void downloadForEmbeddingOrOpeningDone(KonqInterfaces::DownloaderJob *job, const QUrl &url);
0184 
0185 private:
0186 
0187     void embed();
0188     void open();
0189     void execute();
0190     void save();
0191 
0192     bool shouldEmbedThis() const;
0193     void performSave(const QUrl &orig, const QUrl &dest);
0194     void detectArchiveSettings();
0195     void detectSettingsForLocalFiles();
0196     void detectSettingsForRemoteFiles();
0197     void launchMimeTypeFinderJob();
0198     static bool isTextExecutable(const QString &mimeType);
0199     void openExternally();
0200     void killOpenUrlJob();
0201     static bool serviceIsKonqueror(KService::Ptr service);
0202     static bool isMimeTypeKnown(const QString &mimeType);
0203     bool decideEmbedOrSave();
0204     void decideOpenOrSave();
0205     bool embedWithoutAskingToSave(const QString &mimeType);
0206     bool shouldUseDefaultHttpMimeype() const;
0207     void decideAction();
0208     bool isViewLocked() const;
0209 
0210     /**
0211      * @brief Whether the URL we are loading is executable
0212      *
0213      * To be executable, all the following must be true:
0214      * - the URL must be local (we don't want to execute files from the web)
0215      * - the mimetype should be in a list of known executable types (desktop files, executable files, libraries,
0216      *  shell scripts)
0217      * - the executable bit must be set
0218      * @return `true` if the file is executable and `false` otherwise
0219      */
0220     bool isUrlExecutable() const;
0221 
0222     /**
0223      * @brief Checks whether a file downloaded by a part really has the mime type described by the HTML header
0224      *
0225      * This function is called when the part requesting to download an URL wants to perform the download itself,
0226      * after the download has finished. It attempts to determine the mime type both using the file contents and the
0227      * file extension. The algorithm used is the following:
0228      * - determine the mime type using the contents
0229      * - determine the mime type using the extension
0230      * - if the mime type determined from the extension inherits the one determined by content, use the former, otherwise
0231      *  use the latter.
0232      * This way to determine the mimetype is different from that used by `QMimeDataBase::mimeTypeForFile` when called
0233      * with `QMimeDatabase::MatchDefault`, which only checks the content as a fallback.
0234      *
0235      * If the mime type is different from the original one, it replaces the original one and attempts to determine
0236      * again what to do with the file. If the determined mime type is `application/octet-stream`, the original mime type
0237      * is kept.
0238      *
0239      * This function is only called if the user had decided to embed or open the file; it isn't called if the user
0240      * decided to save it (saving doesn't depend on the mime type).
0241      *
0242      * @note This function is needed because sometimes the mime type in the HTTP header is different from the actual
0243      * mimetype of the file.
0244      */
0245     void checkDownloadedMimetype();
0246 
0247     /**
0248      * @brief Finds the part to use to embed the URL
0249      *
0250      * The part is found according to the mimetype and the user preferences. If #m_dontPassToWebEnginePart is `true`
0251      * `webenginepart` won't be returned (even if no other parts are available).
0252      *
0253      * If the `serviceName` variable of #m_request is not empty, that part will be used, regardless of other user
0254      * preferences. If @p forceServiceName is `false`, that part will only be used if it actually supports the mimetype
0255      * @param forceServiceName whether to force the use of the part whose name is in `m_request.serviceName`, even if
0256      * it doesn't support the mimetype
0257      * @return the metadata representing the chosen part or an empty `KPluginMetaData` if no suitable part can be found
0258      * @see m_dontPassToWebEnginePart
0259      */
0260     KPluginMetaData findEmbeddingPart(bool forceServiceName=true) const;
0261 
0262     /**
0263      * @brief Determines #m_mimeType in the constructor
0264      *
0265      * The algorithm used is the following:
0266      * - if #m_mimeType isn't empty or a default mimetype, use it
0267      * - if #m_request.args.mimeType() isn't empty or a default mimetype, use it
0268      * - otherwise use an empty string
0269      */
0270     void determineStartingMimetype();
0271 
0272     /**
0273      * @brief Casts the requesting part or one of its children to a DownloaderInterface*, if possible
0274      * @note Since this uses DownloaderInterface::interface(), it's slower than a simple cast
0275      * @return the requesting part or one of its children casted to a DownloaderInterface* or `nullptr` if
0276      * the requesting part is `nullptr` or neither it nor its children implement DownloaderInterface
0277      */
0278     KonqInterfaces::DownloaderExtension* downloaderInterface() const;
0279 
0280     /**
0281      * @brief Retrieves from the requesting part a DownloaderJob to download the URL
0282      *
0283      * The retrieved job is stored in #m_partDownloaderJob. If downloaderInterface()
0284      * returns `nullptr`, #m_partDownloaderJob is set to `nullptr`
0285      */
0286     void getDownloaderJobFromPart();
0287 
0288     /**
0289      * @brief Downloads the URL using the job provided by the requesting part, if any
0290      *
0291      * If #m_partDownloaderJob is `nullptr`, nothing is done
0292      */
0293     void downloadForEmbeddingOrOpening();
0294 
0295     /**
0296      * @brief Checks whether the given file can be read and, in case of a directory, entered into
0297      *
0298      * If the @p path can't be read, according to `QFileInfo::isReadable()`, it attempts to determine the reason:
0299      * - if the parent directory can be entered (according to `QFileInfo::isExecutable()`) and `QFileInfo::exists() returns `false`,
0300      *   it assumes the file actually doesn't exist
0301      * - if the parent directory can't be entered (according to `QFileInfo::isExecutable()`), it assumes that the permissions don't
0302      *   allow reading for the user.
0303      *
0304      * @return a `KIO::Error` value if @p can't be read or 0 if it can be read and, in case it's a directory, entered. In particular,
0305      *   it returns
0306      *      - `KIO::ERR_DOES_NOT_EXIST` if @p path doesn't exist
0307      *      - `KIO::ERR_CANNOT_OPEN_FOR_READING` if @p path exists but can't be read
0308      *      - `ERR_CANNOT_ENTER_DIRECTORY` if @p path is a directory which can't be entered
0309      */
0310     static int checkAccessToLocalFile(const QString &path);
0311 
0312     typedef QPair<OpenUrlAction, KService::Ptr> OpenSaveAnswer;
0313 
0314     enum class OpenEmbedMode{Open, Embed};
0315 
0316     /**
0317      * @brief Asks the user whether he wants to save the url or open/embed it
0318      *
0319      * This will display the user a `KParts::BrowserOpenOrSaveQuestion` dialog, in open or embed mode depending on the argument.
0320      * When displaying the dialog in "open" mode, it also allows to choose the application to use.
0321      *
0322      * @param mode tells the kind of dialog to show the user: and "Embed or save" dialog if this is OpenEmbedMode::Embed or an "Open
0323      * or save" dialog if it is OpenEmbedMode::Open
0324      * @return an object describing the user's choices. The first element is the action the user has chosen and can only be OpenUrlAction::Save,
0325      * OpenUrlAction::Open, OpenUrlAction::Embed, OpenUrlAction::DoNothing. The second element is the service chosen by the user if @p mode is
0326      * OpenEmbedMode::Open and `nullptr` otherwise.
0327      * @note If @p mode is OpenEmbedMode::Open and the second element of the returned value is `nullptr`, it may mean that the user has chosen the
0328      * "Open with..." entry from the menu displayed by the "Open with..." button. The caller shouldn't try to decide itself which service to use; it
0329      * should instead pass `nullptr` as argument to the KIO::ApplicationLauncherJob constructor, which will take care to display the "Open with..."
0330      * dialog to the user.
0331      */
0332     OpenSaveAnswer askSaveOrOpen(OpenEmbedMode mode) const;
0333     OpenUrlAction decideExecute() const;
0334 
0335 private:
0336     QPointer<KonqMainWindow> m_mainWindow;
0337     QUrl m_url;
0338 
0339     /**
0340      * @brief The mimetype of the URL
0341      *
0342      * If this is empty, it means that the mimetype isn't known and must be determined somehow.
0343      * If this is not empty, it is assumed that its value is the correct mimetype for the URL and
0344      * no other attempts to determine the mimetype will be done (even if this is the default mimetype).
0345      * @see launchMimeTypeFinderJob()
0346      * @see mimetypeDeterminedByJob()
0347      */
0348     QString m_mimeType;
0349     KonqOpenURLRequest m_request;
0350     KonqView *m_view;
0351     bool m_trustedSource;
0352     bool m_dontEmbed;
0353     bool m_ready = false;
0354     bool m_isAsync = false;
0355     OpenUrlAction m_action = OpenUrlAction::UnknwonAction;
0356     KPluginMetaData m_part;
0357     KService::Ptr m_service;
0358     QPointer<KIO::OpenUrlJob> m_openUrlJob;
0359     QPointer<KIO::ApplicationLauncherJob> m_applicationLauncherJob;
0360     QPointer<KIO::MimeTypeFinderJob> m_mimeTypeFinderJob;
0361     QPointer<KonqInterfaces::DownloaderJob> m_partDownloaderJob;
0362     QString m_oldLocationBarUrl;
0363     int m_jobErrorCode = 0;
0364     bool m_dontPassToWebEnginePart;
0365     bool m_protocolAllowsReading;
0366     bool m_letRequestingPartDownloadUrl = false; ///<Whether the URL should be downloaded by the part before opening/embedding/saving it
0367     /**
0368      * @brief Whether only the `embed` action is allowed
0369      *
0370      * If `true`, the only action performed will be to embed the URL. If that action can't be performed for any reason (including
0371      * not having an available part able to display the URL or an error happening), nothing will be done (including displaying error messages).
0372      *
0373      * This variable is set if the metadata in the `KonqOpenURLRequest` passed to the constructor contains the `"EmbedOrNothing"` entry.
0374      *
0375      * @note This is a workaround to allow WebEnginePart to display a downloaded file without risking asking again the user to save
0376      *
0377      * @todo Remove this after a better way to allow the user to quickly display a file after downloading it has been implemented. See comment
0378      * for WebEnginePage::saveUrlToDiskAndDisplay
0379      */
0380     bool m_embedOrNothing = false;
0381 
0382     /**
0383      * @brief The URL that the UrlLoader has been asked to open, in case the requesting parts wants to download it itself
0384      *
0385      * If the requesting part doesn't want to download the URL itself, this is empty. Otherwise, when the UrlLoader is created,
0386      * this is the same as #m_url. After the part has finished downloading the URL, #m_url will contain the temporary local file
0387      * the URL has been downloaded to, while #m_originalUrl will not be changed
0388      */
0389     QUrl m_originalUrl;
0390 };
0391 
0392 QDebug operator<<(QDebug dbg, UrlLoader::OpenUrlAction action);
0393 
0394 #endif // URLLLOADER_H