rebase-hrefs.cpp 7.71 KB
Newer Older
Max Gaukler's avatar
Max Gaukler committed
1 2 3 4 5 6 7 8 9
// SPDX-License-Identifier: GPL-2.0-or-later
/** @file
 * TODO: insert short description here
 *//*
 * Authors: see git history
 *
 * Copyright (C) 2018 Authors
 * Released under GNU GPL v2+, read the file 'COPYING' for more information.
 */
10 11 12 13 14

#include <glibmm/convert.h>
#include <glibmm/miscutils.h>
#include <glibmm/uriutils.h>

15
#include "../document.h"  /* Unfortunately there's a separate xml/document.h. */
16
#include "streq.h"
17

18
#include "io/dir-util.h"
19 20 21
#include "io/sys.h"

#include "object/sp-object.h"
22
#include "object/uri.h"
23

24
#include "xml/node.h"
25
#include "xml/rebase-hrefs.h"
26

27
using Inkscape::XML::AttributeRecord;
28 29

/**
30
 * Determine if a href needs rebasing.
31
 */
32
static bool href_needs_rebasing(std::string const &href)
33
{
34
    bool ret = true;
35

36 37
    if ( href.empty() || (href[0] == '#') ) {
        ret = false;
38 39 40
        /* False (no change) is the right behaviour even when the base URI differs from the
         * document URI: RFC 3986 defines empty string relative URL as referring to the containing
         * document, rather than referring to the base URI. */
41 42 43 44
    } else {
        /* Don't change data or http hrefs. */
        std::string scheme = Glib::uri_parse_scheme(href);
        if ( !scheme.empty() ) {
45 46 47 48 49 50
            /* Assume it shouldn't be changed.  This is probably wrong if the scheme is `file'
             * (or if the scheme of the new base is non-file, though I believe that never
             * happens at the time of writing), but that's rare, and we won't try too hard to
             * handle this now: wait until after the freeze, then add liburiparser (or similar)
             * as a dependency and do it properly.  For now we'll just try to be simple (while
             * at least still correctly handling data hrefs). */
51
            ret = false;
52 53 54
        }
    }

55
    return ret;
56 57 58 59 60 61 62 63 64 65 66 67
}

Inkscape::Util::List<AttributeRecord const>
Inkscape::XML::rebase_href_attrs(gchar const *const old_abs_base,
                                 gchar const *const new_abs_base,
                                 Inkscape::Util::List<AttributeRecord const> attributes)
{
    using Inkscape::Util::List;
    using Inkscape::Util::cons;
    using Inkscape::Util::ptr_shared;
    using Inkscape::Util::share_string;

68

69 70 71 72 73 74 75 76 77 78 79
    if (old_abs_base == new_abs_base) {
        return attributes;
    }

    GQuark const href_key = g_quark_from_static_string("xlink:href");
    GQuark const absref_key = g_quark_from_static_string("sodipodi:absref");

    /* First search attributes for xlink:href and sodipodi:absref, putting the rest in ret.
     *
     * However, if we find that xlink:href doesn't need rebasing, then return immediately
     * with no change to attributes. */
80 81
    ptr_shared old_href;
    ptr_shared sp_absref;
82 83 84 85 86
    List<AttributeRecord const> ret;
    {
        for (List<AttributeRecord const> ai(attributes); ai; ++ai) {
            if (ai->key == href_key) {
                old_href = ai->value;
87
                if (!href_needs_rebasing(static_cast<char const *>(old_href))) {
88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106
                    return attributes;
                }
            } else if (ai->key == absref_key) {
                sp_absref = ai->value;
            } else {
                ret = cons(AttributeRecord(ai->key, ai->value), ret);
            }
        }
    }

    if (!old_href) {
        return attributes;
        /* We could instead return ret in this case, i.e. ensure that sodipodi:absref is cleared if
         * no xlink:href attribute.  However, retaining it might be more cautious.
         *
         * (For the usual case of not present, attributes and ret will be the same except
         * reversed.) */
    }

107 108 109 110 111 112 113 114
    auto uri = URI::from_href_and_basedir(static_cast<char const *>(old_href), old_abs_base);
    auto abs_href = uri.toNativeFilename();

    if (!Inkscape::IO::file_test(abs_href.c_str(), G_FILE_TEST_EXISTS) &&
        Inkscape::IO::file_test(sp_absref, G_FILE_TEST_EXISTS)) {
        uri = URI::from_native_filename(sp_absref);
    }

115 116 117 118 119 120
    std::string baseuri;
    if (new_abs_base) {
        baseuri = URI::from_dirname(new_abs_base).str();
    }

    auto new_href = uri.str(baseuri.c_str());
121

122
    ret = cons(AttributeRecord(href_key, share_string(new_href.c_str())), ret); // Check if this is safe/copied or if it is only held.
123 124 125
    if (sp_absref) {
        /* We assume that if there wasn't previously a sodipodi:absref attribute
         * then we shouldn't create one. */
126
        ret = cons(AttributeRecord(absref_key, ( streq(abs_href.c_str(), sp_absref)
127
                                                 ? sp_absref
128
                                                 : share_string(abs_href.c_str()) )),
129 130
                   ret);
    }
131

132 133 134
    return ret;
}

135
void Inkscape::XML::rebase_hrefs(SPDocument *const doc, gchar const *const new_base, bool const spns)
136
{
137
    using Inkscape::URI;
Dmitry Kirsanov's avatar
Dmitry Kirsanov committed
138

139
    std::string old_base_url_str = URI::from_dirname(doc->getDocumentBase()).str();
140 141 142 143 144
    std::string new_base_url_str;

    if (new_base) {
        new_base_url_str = URI::from_dirname(new_base).str();
    }
145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165

    /* TODO: Should handle not just image but also:
     *
     *    a, altGlyph, animElementAttrs, animate, animateColor, animateMotion, animateTransform,
     *    animation, audio, color-profile, cursor, definition-src, discard, feImage, filter,
     *    font-face-uri, foreignObject, glyphRef, handler, linearGradient, mpath, pattern,
     *    prefetch, radialGradient, script, set, textPath, tref, use, video
     *
     * (taken from the union of the xlink:href elements listed at
     * http://www.w3.org/TR/SVG11/attindex.html and
     * http://www.w3.org/TR/SVGMobile12/attributeTable.html).
     *
     * Also possibly some other attributes of type <URI> or <IRI> or list-thereof, or types like
     * <paint> that can include an IRI/URI, and stylesheets and style attributes.  (xlink:base is a
     * special case.  xlink:role and xlink:arcrole can be assumed to be already absolute, based on
     * http://www.w3.org/TR/SVG11/struct.html#xlinkRefAttrs .)
     *
     * Note that it may not useful to set sodipodi:absref for anything other than image.
     *
     * Note also that Inkscape only supports fragment hrefs (href="#pattern257") for many of these
     * cases. */
166 167
    std::vector<SPObject *> images = doc->getResourceList("image");
    for (std::vector<SPObject *>::const_iterator it = images.begin(); it != images.end(); ++it) {
168
        Inkscape::XML::Node *ir = (*it)->getRepr();
169

170 171 172
        auto href_cstr = ir->attribute("xlink:href");
        if (!href_cstr) {
            continue;
173
        }
174 175 176 177

        // skip fragment URLs
        if (href_cstr[0] == '#') {
            continue;
178
        }
179 180 181 182 183 184 185

        // make absolute
        URI url;
        try {
            url = URI(href_cstr, old_base_url_str.c_str());
        } catch (...) {
            continue;
186 187
        }

188 189 190
        // skip non-file URLs
        if (!url.hasScheme("file")) {
            continue;
191 192
        }

193 194 195 196 197 198 199 200
        // if path doesn't exist, use sodipodi:absref
        if (!g_file_test(url.toNativeFilename().c_str(), G_FILE_TEST_EXISTS)) {
            auto spabsref = ir->attribute("sodipodi:absref");
            if (spabsref && g_file_test(spabsref, G_FILE_TEST_EXISTS)) {
                url = URI::from_native_filename(spabsref);
            }
        } else if (spns) {
            ir->setAttribute("sodipodi:absref", url.toNativeFilename());
201
        }
202 203

        auto href_str = url.str(new_base_url_str.c_str());
204 205
        href_str = Inkscape::uri_to_iri(href_str.c_str());

206
        ir->setAttribute("xlink:href", href_str);
207
    }
208

209
    doc->setDocumentBase(new_base);
210 211 212 213 214 215 216 217 218 219 220 221
}


/*
  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:
*/
222
// vi: set autoindent shiftwidth=4 tabstop=8 filetype=cpp expandtab softtabstop=4 fileencoding=utf-8 textwidth=99 :