From 89129c4e71a5a2af0b4d249c261e30e8e6532158 Mon Sep 17 00:00:00 2001 From: Thomas Holder Date: Sat, 3 Nov 2018 19:44:23 +0000 Subject: Inkscape::URI API enhancements --- src/object/uri.cpp | 222 +++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 197 insertions(+), 25 deletions(-) (limited to 'src/object/uri.cpp') 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 +#include +#include +#include #include #include @@ -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 "[][;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: -- cgit v1.2.3