summaryrefslogtreecommitdiffstats
path: root/src/extension/dependency.cpp
blob: 09928c64f755331d5dd04c4bd9d9b19ea4ea38b0 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
// SPDX-License-Identifier: GPL-2.0-or-later
/*
 * Author:
 *   Ted Gould <ted@gould.cx>
 *
 * Copyright (C) 2006 Johan Engelen, johan@shouraizou.nl
 * Copyright (C) 2004 Author
 *
 * Released under GNU GPL v2+, read the file 'COPYING' for more information.
 */

#include <glibmm/i18n.h>
#include <glibmm/fileutils.h>
#include <glibmm/miscutils.h>
#include "dependency.h"
#include "db.h"
#include "extension.h"
#include "io/resource.h"

namespace Inkscape {
namespace Extension {

// These strings are for XML attribute comparisons and should not be translated;
// make sure to keep in sync with enum defined in dependency.h
gchar const * Dependency::_type_str[] = {
    "executable",
    "file",
    "extension",
};

// These strings are for XML attribute comparisons and should not be translated
// make sure to keep in sync with enum defined in dependency.h
gchar const * Dependency::_location_str[] = {
    "path",
    "extensions",
    "inx",
    "absolute",
};

/**
    \brief   Create a dependency using an XML definition
    \param   in_repr       XML definition of the dependency
    \param   extension     Reference to the extension requesting this dependency
    \param   default_type  Default file type of the dependency (unless overridden by XML definition's "type" attribute)

    This function mostly looks for the 'location' and 'type' attributes
    and turns them into the enums of the same name.  This makes things
    a little bit easier to use later.  Also, a pointer to the core
    content is pulled out -- also to make things easier.
*/
Dependency::Dependency (Inkscape::XML::Node * in_repr, const Extension *extension, type_t default_type)
    : _repr(in_repr)
    , _extension(extension)
    , _type(default_type)
{
    Inkscape::GC::anchor(_repr);

    if (const gchar * location = _repr->attribute("location")) {
        for (int i = 0; i < LOCATION_CNT && location != nullptr; i++) {
            if (!strcmp(location, _location_str[i])) {
                _location = (location_t)i;
                break;
            }
        }
    } else if (const gchar * location = _repr->attribute("reldir")) { // backwards-compatibility
        for (int i = 0; i < LOCATION_CNT && location != nullptr; i++) {
            if (!strcmp(location, _location_str[i])) {
                _location = (location_t)i;
                break;
            }
        }
    }

    const gchar * type = _repr->attribute("type");
    for (int i = 0; i < TYPE_CNT && type != nullptr; i++) {
        if (!strcmp(type, _type_str[i])) {
            _type = (type_t)i;
            break;
        }
    }

    _string = _repr->firstChild()->content();

    _description = _repr->attribute("description");
    if (_description == nullptr)
        _description = _repr->attribute("_description");

    return;
}

/**
    \brief   This dependency is not longer needed

    Unreference the XML structure.
*/
Dependency::~Dependency ()
{
    Inkscape::GC::release(_repr);
}

/**
    \brief   Check if the dependency passes.
    \return  Whether or not the dependency passes.

    This function depends largely on all of the enums.  The first level
    that is evaluated is the \c _type.

    If the type is \c TYPE_EXTENSION then the id for the extension is
    looked up in the database.  If the extension is found, and it is
    not deactivated, the dependency passes.

    If the type is \c TYPE_EXECUTABLE or \c TYPE_FILE things are getting
    even more interesting because now the \c _location variable is also
    taken into account.  First, the difference between the two is that
    the file test for \c TYPE_EXECUTABLE also tests to make sure the
    file is executable, besides checking that it exists.

    If the \c _location is \c LOCATION_EXTENSIONS then the \c INKSCAPE_EXTENSIONDIR
    is put on the front of the string with \c build_filename.  Then the
    appropriate filetest is run.

    If the \c _location is \c LOCATION_ABSOLUTE then the file test is
    run directly on the string.

    If the \c _location is \c LOCATION_PATH or not specified then the
    path is used to find the file.  Each entry in the path is stepped
    through, attached to the string, and then tested.  If the file is
    found then a TRUE is returned.  If we get all the way through the
    path then a FALSE is returned, the command could not be found.
*/
bool Dependency::check ()
{
    if (_string == nullptr) {
        return false;
    }

    _absolute_location = "";

    switch (_type) {
        case TYPE_EXTENSION: {
            Extension * myext = db.get(_string);
            if (myext == nullptr) return false;
            if (myext->deactivated()) return false;
            break;
        }
        case TYPE_EXECUTABLE:
        case TYPE_FILE: {
            Glib::FileTest filetest = Glib::FILE_TEST_EXISTS;

            std::string location(_string);

            // get potential file extension for later usage
            std::string extension;
            size_t index = location.find_last_of(".");
            if (index != std::string::npos) {
                extension = location.substr(index);
            }

            // check interpreted scripts as "file" for backwards-compatibility, even if "executable" was requested
            static const std::vector<std::string> interpreted = {".py", ".pl", ".rb"};
            if (!extension.empty() &&
                    std::find(interpreted.begin(), interpreted.end(), extension) != interpreted.end())
            {
                _type = TYPE_FILE;
            }

#ifndef _WIN32
            // There's no executable bit on Windows, so this is unreliable
            // glib would search for "executable types" instead, which are only {".exe", ".cmd", ".bat", ".com"},
            // and would therefore miss files without extension and other script files (like .py files)
            if (_type == TYPE_EXECUTABLE) {
                filetest = Glib::FILE_TEST_IS_EXECUTABLE;
            }
#endif

            switch (_location) {
                 // backwards-compatibility: location="extensions" will be deprecated as of Inkscape 1.1,
                 //                          use location="inx" instead
                case LOCATION_EXTENSIONS: {
                    // get_filename will warn if the resource isn't found, while returning an empty string.
                    std::string temploc =
                        Inkscape::IO::Resource::get_filename_string(Inkscape::IO::Resource::EXTENSIONS, location.c_str());
                    if (!temploc.empty()) {
                        location = temploc;
                        _absolute_location = temploc;
                        break;
                    }
                    /* Look for deprecated locations next */
                    auto deprloc = g_build_filename("inkex", "deprecated-simple", location.c_str(), nullptr);
                    std::string tempdepr =
                        Inkscape::IO::Resource::get_filename_string(Inkscape::IO::Resource::EXTENSIONS, deprloc, false, true);
                    g_free(deprloc);
                    if (!tempdepr.empty()) {
                        location = tempdepr;
                        _absolute_location = tempdepr;
                        break;
                    }
                // PASS THROUGH!!! - also check inx location for backwards-compatibility,
                //                   notably to make extension manager work
                //                   (installs into subfolders of "extensions" directory)
                }
                case LOCATION_INX: {
                    std::string base_directory = _extension->get_base_directory();
                    if (base_directory.empty()) {
                        g_warning("Dependency '%s' requests location relative to .inx file, "
                                  "which is unknown for extension '%s'", _string, _extension->get_id());
                    }
                    std::string absolute_location = Glib::build_filename(base_directory, location);
                    if (!Glib::file_test(absolute_location, filetest)) {
                        return false;
                    }
                    _absolute_location = absolute_location;
                    break;
                }
                case LOCATION_ABSOLUTE: {
                    // TODO: should we check if the directory actually is absolute and/or sanitize the filename somehow?
                    if (!Glib::file_test(location, filetest)) {
                        return false;
                    }
                    _absolute_location = location;
                    break;
                }
                /* The default case is to look in the path */
                case LOCATION_PATH:
                default: {
                    // TODO: we can likely use g_find_program_in_path (or its glibmm equivalent) for executable types

                    gchar * path = g_strdup(g_getenv("PATH"));

                    if (path == nullptr) {
                        /* There is no `PATH' in the environment.
                           The default search path is the current directory */
                        path = g_strdup(G_SEARCHPATH_SEPARATOR_S);
                    }

                    gchar * orig_path = path;

                    for (; path != nullptr;) {
                        gchar * local_path; // to have the path after detection of the separator
                        std::string final_name;

                        local_path = path;
                        path = g_utf8_strchr(path, -1, G_SEARCHPATH_SEPARATOR);
                        /* Not sure whether this is UTF8 happy, but it would seem
                           like it considering that I'm searching (and finding)
                           the ':' character */
                        if (path != nullptr) {
                            path[0] = '\0';
                            path++;
                        }

                        if (*local_path == '\0') {
                            final_name = _string;
                        } else {
                            final_name = Glib::build_filename(local_path, _string);
                        }

                        if (Glib::file_test(final_name, filetest)) {
                            g_free(orig_path);
                            _absolute_location = final_name;
                            return true;
                        }

#ifdef _WIN32
                        // Unfortunately file extensions tend to be different on Windows and we can't know
                        // which one it is, so try all extensions glib assumes to be executable.
                        // As we can only guess here, return the version without extension if either one is found,
                        // so that we don't accidentally override (or conflict with) some g_spawn_* magic.
                        if (_type == TYPE_EXECUTABLE) {
                            static const std::vector<std::string> extensions = {".exe", ".cmd", ".bat", ".com"};
                            if (extension.empty() ||
                                    std::find(extensions.begin(), extensions.end(), extension) == extensions.end())
                            {
                                for (auto extension : extensions) {
                                    if (Glib::file_test(final_name + extension, filetest)) {
                                        g_free(orig_path);
                                        _absolute_location = final_name;
                                        return true;
                                    }
                                }
                            }
                        }
#endif
                    }

                    g_free(orig_path);
                    return false; /* Reverse logic in this one */
                }
            } /* switch _location */
            break;
        } /* TYPE_FILE, TYPE_EXECUTABLE */
        default:
            return false;
    } /* switch _type */

    return true;
}

/**
    \brief   Accessor to the name attribute.
    \return  A string containing the name of the dependency.

    Returns the name of the dependency as found in the configuration file.

*/
const gchar* Dependency::get_name()
{
    return _string;
}

/**
    \brief   Path of this dependency
    \return  Absolute path to the dependency file
             (or an empty string if dependency was not found or is of TYPE_EXTENSION)

    Returns the verified absolute path of the dependency file.
    This value is only available after checking the Dependency by calling Dependency::check().
*/
std::string Dependency::get_path()
{
    if (_type == TYPE_EXTENSION) {
        g_warning("Requested absolute path of dependency '%s' which is of 'extension' type.", _string);
        return "";
    }
    if (_absolute_location == UNCHECKED) {
        g_warning("Requested absolute path of dependency '%s' which is unchecked.", _string);
        return "";
    }

    return _absolute_location;
}

/**
    \brief   Print out a dependency to a string.
*/
Glib::ustring Dependency::info_string()
{
    Glib::ustring str = Glib::ustring::compose("%1:\n\t%2: %3\n\t%4: %5\n\t%6: %7",
                                               _("Dependency"),
                                               _("type"),     _(_type_str[_type]),
                                               _("location"), _(_location_str[_location]),
                                               _("string"),     _string);

    if (_description) {
        str += Glib::ustring::compose("\n\t%1: %2\n", _("  description: "), _(_description));
    }

    return str;
}

} }  /* namespace Inkscape, Extension */

/*
  Local Variables:
  mode:c++
  c-file-style:"stroustrup"
  c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
  indent-tabs-mode:nil
  fill-column:99
  End:
*/
// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :