summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorThomas Holder <thomas@thomas-holder.de>2018-11-03 19:44:23 +0000
committerThomas Holder <thomas@thomas-holder.de>2018-11-03 19:44:23 +0000
commit89129c4e71a5a2af0b4d249c261e30e8e6532158 (patch)
tree0f6ad42b76ac2be46d049a23c61d7e85a21c4bdf /src
parentMinor yaxis fix for meassure tool (diff)
downloadinkscape-89129c4e71a5a2af0b4d249c261e30e8e6532158.tar.gz
inkscape-89129c4e71a5a2af0b4d249c261e30e8e6532158.zip
Inkscape::URI API enhancements
Diffstat (limited to 'src')
-rw-r--r--src/object/sp-image.cpp47
-rw-r--r--src/object/uri.cpp222
-rw-r--r--src/object/uri.h86
-rw-r--r--src/style-internal.cpp4
4 files changed, 271 insertions, 88 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);
diff --git a/src/style-internal.cpp b/src/style-internal.cpp
index fd3510c94..186d382f0 100644
--- a/src/style-internal.cpp
+++ b/src/style-internal.cpp
@@ -1335,7 +1335,7 @@ const Glib::ustring SPIPaint::get_value() const
// url must go first as other values can serve as fallbacks
auto ret = Glib::ustring("");
if (this->value.href && this->value.href->getURI()) {
- ret += this->value.href->getURI()->toStdString(true);
+ ret += this->value.href->getURI()->cssStr();
}
switch(this->paintOrigin) {
case SP_CSS_PAINT_ORIGIN_CURRENT_COLOR:
@@ -1698,7 +1698,7 @@ SPIFilter::read( gchar const *str ) {
const Glib::ustring SPIFilter::get_value() const
{
if (this->inherit) return Glib::ustring("inherit");
- if (this->href) return this->href->getURI()->toStdString(true);
+ if (this->href) return this->href->getURI()->cssStr();
return Glib::ustring("");
}