diff options
Diffstat (limited to 'src/object')
| -rw-r--r-- | src/object/sp-image.cpp | 47 | ||||
| -rw-r--r-- | src/object/uri.cpp | 222 | ||||
| -rw-r--r-- | src/object/uri.h | 86 |
3 files changed, 269 insertions, 86 deletions
diff --git a/src/object/sp-image.cpp b/src/object/sp-image.cpp index 69565c992..a66bb3625 100644 --- a/src/object/sp-image.cpp +++ b/src/object/sp-image.cpp @@ -580,52 +580,21 @@ Inkscape::Pixbuf *sp_image_repr_read_image(gchar const *href, gchar const *absre gchar const *filename = href; if (filename != nullptr) { - if (strncmp (filename,"file:",5) == 0) { - gchar *fullname = g_filename_from_uri(filename, nullptr, nullptr); - if (fullname) { - inkpb = Inkscape::Pixbuf::create_from_file(fullname, svgdpi); - g_free(fullname); - if (inkpb != nullptr) { - return inkpb; - } - } - } else if (strncmp (filename,"data:",5) == 0) { + if (g_ascii_strncasecmp(filename, "data:", 5) == 0) { /* data URI - embedded image */ filename += 5; inkpb = Inkscape::Pixbuf::create_from_data_uri(filename, svgdpi); - if (inkpb != nullptr) { - return inkpb; - } } else { + auto url = Inkscape::URI::from_href_and_basedir(href, base); - if (!g_path_is_absolute (filename)) { - /* try to load from relative pos combined with document base*/ - const gchar *docbase = base; - if (!docbase) { - docbase = "."; - } - gchar *fullname = g_build_filename(docbase, filename, NULL); - - // document base can be wrong (on the temporary doc when importing bitmap from a - // different dir) or unset (when doc is not saved yet), so we check for base+href existence first, - // and if it fails, we also try to use bare href regardless of its g_path_is_absolute - if (g_file_test (fullname, G_FILE_TEST_EXISTS) && !g_file_test (fullname, G_FILE_TEST_IS_DIR)) { - inkpb = Inkscape::Pixbuf::create_from_file(fullname, svgdpi); - if (inkpb != nullptr) { - g_free (fullname); - return inkpb; - } - } - g_free (fullname); + if (url.hasScheme("file")) { + auto native = url.toNativeFilename(); + inkpb = Inkscape::Pixbuf::create_from_file(native.c_str(), svgdpi); } + } - /* try filename as absolute */ - if (g_file_test (filename, G_FILE_TEST_EXISTS) && !g_file_test (filename, G_FILE_TEST_IS_DIR)) { - inkpb = Inkscape::Pixbuf::create_from_file(filename, svgdpi); - if (inkpb != nullptr) { - return inkpb; - } - } + if (inkpb != nullptr) { + return inkpb; } } diff --git a/src/object/uri.cpp b/src/object/uri.cpp index 91878512a..499e4456c 100644 --- a/src/object/uri.cpp +++ b/src/object/uri.cpp @@ -10,6 +10,10 @@ #include "uri.h" +#include <giomm/contenttype.h> +#include <giomm/file.h> +#include <glibmm/base64.h> +#include <glibmm/convert.h> #include <glibmm/ustring.h> #include <glibmm/miscutils.h> @@ -17,6 +21,24 @@ namespace Inkscape { +auto const URI_ALLOWED_NON_ALNUM = "!#$%&'()*+,-./:;=?@_~"; + +/** + * Return true if the given URI string contains characters that need escaping. + * + * Note: It does not check if valid characters appear in invalid context (e.g. + * '%' not followed by two hex digits). + */ +static bool uri_needs_escaping(char const *uri) +{ + for (auto *p = uri; *p; ++p) { + if (!g_ascii_isalnum(*p) && !strchr(URI_ALLOWED_NON_ALNUM, *p)) { + return true; + } + } + return false; +} + URI::URI() { const gchar *in = ""; _impl = Impl::create(xmlParseURI(in)); @@ -27,18 +49,59 @@ URI::URI(const URI &uri) { _impl = uri._impl; } -URI::URI(gchar const *preformed) { +URI::URI(gchar const *preformed, char const *baseuri) +{ xmlURIPtr uri; if (!preformed) { throw MalformedURIException(); } + + // check for invalid characters, escape if needed + xmlChar *escaped = nullptr; + if (uri_needs_escaping(preformed)) { + escaped = xmlURIEscapeStr( // + (xmlChar const *)preformed, // + (xmlChar const *)URI_ALLOWED_NON_ALNUM); + preformed = (decltype(preformed))escaped; + } + + // make absolute + xmlChar *full = nullptr; + if (baseuri) { + full = xmlBuildURI( // + (xmlChar const *)preformed, // + (xmlChar const *)baseuri); +#if LIBXML_VERSION < 20905 + // libxml2 bug: "file:/some/file" instead of "file:///some/file" + auto f = (gchar const *)full; + if (f && g_str_has_prefix(f, "file:/") && f[6] != '/') { + auto fixed = std::string(f, 6) + "//" + std::string(f + 6); + xmlFree(full); + full = (xmlChar *)xmlMemStrdup(fixed.c_str()); + } +#endif + preformed = (decltype(preformed))full; + } + uri = xmlParseURI(preformed); + + if (full) { + xmlFree(full); + } + if (escaped) { + xmlFree(escaped); + } if (!uri) { throw MalformedURIException(); } _impl = Impl::create(uri); } +URI::URI(char const *preformed, URI const &baseuri) + : URI::URI(preformed, baseuri.str().c_str()) +{ +} + URI::~URI() { _impl->unreference(); } @@ -134,13 +197,6 @@ const gchar *URI::Impl::getOpaque() const { return (gchar *)_uri->opaque; } -gchar *URI::to_native_filename(gchar const* uri) -{ - gchar *filename = nullptr; - URI tmp(uri); - filename = tmp.toNativeFilename(); - return filename; -} /* * Returns the absolute path to an existing file referenced in this URI, * if the uri is data, the path is empty or the file doesn't exist, then @@ -148,16 +204,28 @@ gchar *URI::to_native_filename(gchar const* uri) * * Does not check if the returned path is the local document's path (local) * and thus redundent. Caller is expected to check against the document's path. + * + * @param base directory name to use as base if this is not an absolute URL */ const std::string URI::getFullPath(std::string const &base) const { if (!_impl->getPath()) { return ""; } - std::string path = std::string(_impl->getPath()); - // Calculate the absolute path from an available base - if(!base.empty() && !path.empty() && path[0] != '/') { - path = Glib::build_filename(base, path); + + URI url; + + if (!base.empty() && !getScheme()) { + url = Inkscape::URI::from_href_and_basedir(str().c_str(), base.c_str()); + } else { + url = *this; + } + + if (!url.hasScheme("file")) { + return ""; } + + auto path = Glib::filename_from_uri(url.str()); + // Check the existence of the file if(! g_file_test(path.c_str(), G_FILE_TEST_EXISTS) || g_file_test(path.c_str(), G_FILE_TEST_IS_DIR) ) { @@ -168,19 +236,9 @@ const std::string URI::getFullPath(std::string const &base) const { /* TODO !!! proper error handling */ -gchar *URI::toNativeFilename() const { - gchar *uriString = toString(); - if (isRelativePath()) { - return uriString; - } else { - gchar *filename = g_filename_from_uri(uriString, nullptr, nullptr); - g_free(uriString); - if (filename) { - return filename; - } else { - throw MalformedURIException(); - } - } +std::string URI::toNativeFilename() const +{ // + return Glib::filename_from_uri(str()); } URI URI::fromUtf8( gchar const* path ) { @@ -222,6 +280,27 @@ URI URI::from_native_filename(gchar const *path) { return result; } +URI URI::from_dirname(gchar const *path) +{ + std::string pathstr = path ? path : "."; + + if (!Glib::path_is_absolute(pathstr)) { + pathstr = Glib::build_filename(Glib::get_current_dir(), pathstr); + } + + auto uristr = Glib::filename_to_uri(pathstr) + "/"; + return URI(uristr.c_str()); +} + +URI URI::from_href_and_basedir(char const *href, char const *basedir) +{ + try { + return URI(href, URI::from_dirname(basedir)); + } catch (...) { + return URI(); + } +} + gchar *URI::Impl::toString() const { xmlChar *string = xmlSaveUri(_uri); if (string) { @@ -234,8 +313,101 @@ gchar *URI::Impl::toString() const { } } +std::string URI::str(char const *baseuri) const +{ + std::string s; + gchar *save = _impl->toString(); + if (save) { + xmlChar *rel = nullptr; + const char *latest = save; + if (baseuri) { + rel = xmlBuildRelativeURI((xmlChar *)save, (xmlChar *)baseuri); + if (rel) { + latest = (const char *)rel; + } + } + s = latest; + if (rel) { + xmlFree(rel); + } + g_free(save); + } + return s; +} + +std::string URI::getMimeType() const +{ + const char *path = getPath(); + + if (path) { + if (hasScheme("data")) { + for (const char *p = path; *p; ++p) { + if (*p == ';' || *p == ',') { + return std::string(path, p); + } + } + } else { + bool uncertain; + auto type = Gio::content_type_guess(path, nullptr, 0, uncertain); + return Gio::content_type_get_mime_type(type).raw(); + } + } + + return "unknown/unknown"; +} + +std::string URI::getContents() const +{ + if (hasScheme("data")) { + // handle data URIs + + const char *p = getPath(); + const char *tok = nullptr; + + // scan "[<media type>][;base64]," header + for (; *p && *p != ','; ++p) { + if (*p == ';') { + tok = p + 1; + } + } + + // body follows after comma + if (*p != ',') { + g_critical("data URI misses comma"); + } else if (tok && strncmp("base64", tok, p - tok) == 0) { + // base64 encoded body + return Glib::Base64::decode(p + 1); + } else { + // raw body + return p + 1; + } + } else { + // handle non-data URIs with GVfs + auto file = Gio::File::create_for_uri(str()); + + gsize length = 0; + char *buffer = nullptr; + + if (file->load_contents(buffer, length)) { + auto contents = std::string(buffer, buffer + length); + g_free(buffer); + return contents; + } else { + g_critical("failed to load contents from %.100s", str().c_str()); + } + } + + return ""; } +bool URI::hasScheme(const char *scheme) const +{ + const char *s = getScheme(); + return s && g_ascii_strcasecmp(s, scheme) == 0; +} + +} // namespace Inkscape + /* Local Variables: diff --git a/src/object/uri.h b/src/object/uri.h index b2cdcf778..8cfccfed0 100644 --- a/src/object/uri.h +++ b/src/object/uri.h @@ -19,6 +19,17 @@ namespace Inkscape { /** * Represents an URI as per RFC 2396. + * + * Typical use-cases of this class: + * - converting between relative and absolute URIs + * - converting URIs to/from filenames (alternative: Glib functions, but those only handle absolute paths) + * - generic handling of data/file/http URIs (e.g. URI::getContents and URI::getMimeType) + * + * Wraps libxml2's URI functions. Direct usage of libxml2's C-API is discouraged if favor of + * Inkscape::URI. (At the time of writing this, no de-factor standard C++ URI library exists, so + * wrapping libxml2 seems like a good solution) + * + * Implementation detail: Immutable type, copies share a ref-counted data pointer. */ class URI { public: @@ -35,8 +46,12 @@ public: * Constructor from a C-style ASCII string. * * @param preformed Properly quoted C-style string to be represented. + * @param baseuri If @a preformed is a relative URI, use @a baseuri to make it absolute + * + * @throw MalformedURIException */ - explicit URI(char const *preformed); + explicit URI(char const *preformed, char const *baseuri = nullptr); + explicit URI(char const *preformed, URI const &baseuri); /** * Destructor. @@ -63,7 +78,7 @@ public: /** * Determines if the relative URI represented is a 'net-path' as per RFC 2396. * - * A net-path is one that starts with "\\". + * A net-path is one that starts with "//". * * @return \c true if the URI is relative and a net-path, \c false otherwise. */ @@ -81,7 +96,7 @@ public: /** * Determines if the relative URI represented is a 'absolute-path' as per RFC 2396. * - * An absolute-path is one that starts with a single "\". + * An absolute-path is one that starts with a single "/". * * @return \c true if the URI is relative and an absolute-path, \c false otherwise. */ @@ -101,11 +116,25 @@ public: static URI from_native_filename(char const *path); - static char *to_native_filename(char const* uri); + /** + * URI of a local directory. The URI path will end with a slash. + */ + static URI from_dirname(char const *path); + + /** + * Convenience function for the common use case given a xlink:href attribute and a local + * directory as the document base. Returns an empty URI on failure. + */ + static URI from_href_and_basedir(char const *href, char const *basedir); const std::string getFullPath(std::string const &base) const; - char *toNativeFilename() const; + /** + * Convert this URI to a native filename. + * + * @throw Glib::ConvertError If this is not a "file" URI + */ + std::string toNativeFilename() const; /** * Returns a glib string version of this URI. @@ -113,30 +142,43 @@ public: * The returned string must be freed with \c g_free(). * * @return a glib string version of this URI. + * + * @todo remove this method and use str() instead */ char *toString() const { return _impl->toString(); } /** - * Return a more useful std::string with optional url(...) - * useful for css printing. - */ - std::string toStdString(bool with_braces=false) const { - char *uri = this->toString(); - auto ret = std::string(""); - if(uri) { - if(with_braces) { - ret += "url("; - ret += uri; - ret += ")"; - } else { - ret += uri; - } - free((void *) uri); - } - return ret; + * Return the string representation of this URI + * + * @param baseuri Return a relative path if this URI shares protocol and host with @a baseuri + */ + std::string str(char const *baseuri = nullptr) const; + + /** + * Get the MIME type (e.g.\ "image/png") + */ + std::string getMimeType() const; + + /** + * Return the contents of the file + */ + std::string getContents() const; + + /** + * Return a CSS formatted url value + * + * @param baseuri Return a relative path if this URI shares protocol and host with @a baseuri + */ + std::string cssStr(char const *baseuri = nullptr) const { + return "url(" + str(baseuri) + ")"; } /** + * True if the scheme equals the given string (not case sensitive) + */ + bool hasScheme(const char *scheme) const; + + /** * Assignment operator. */ URI &operator=(URI const &uri); |
