3f5d08
From 44505cb397c46baa7dd4a0456f737f36e6d19ad0 Mon Sep 17 00:00:00 2001
3f5d08
From: Christian Persch <chpe@src.gnome.org>
3f5d08
Date: Tue, 1 Jan 2019 18:16:18 +0100
3f5d08
Subject: [PATCH] glib: Fix named destinations
3f5d08
MIME-Version: 1.0
3f5d08
Content-Type: text/plain; charset=UTF-8
3f5d08
Content-Transfer-Encoding: 8bit
3f5d08
3f5d08
Named destinations may be described by bytestrings, containing
3f5d08
embedded NULs and not being NUL terminated. That means they cannot
3f5d08
be exposed directly as char*.
3f5d08
3f5d08
The alternatives are to escape the string from the internal representation
3f5d08
when exposing it in the API (e.g. in PopplerDest.named_dest), or to
3f5d08
add parallel API exposing it as GString, or GBytes. This patch chooses
3f5d08
the first option, since the presence of these named destionations in the
3f5d08
public, not sealed, PopplerDest struct means that the second option would
3f5d08
need more API additions. The chosen option is simpler, and does not
3f5d08
need the API users to adapt unless they create the named dest strings
3f5d08
themselves, or consume them in ways other than calling poppler APIs.
3f5d08
3f5d08
The escaping scheme chosen simply replaces embedded NUL with "\0" and
3f5d08
escapes a literal backslash with "\\".  This is a minimal ABI change in
3f5d08
that some strings that previously worked unchanged as destinations
3f5d08
(those containing backslash) now don't work, but on the other hand,
3f5d08
previously it was impossible to use any destinations containing embedded
3f5d08
NULs.
3f5d08
3f5d08
Add poppler_named_dest_{from,to}_bytestring() to perform that
3f5d08
conversion, and clarify the documentation for when you need them.
3f5d08
3f5d08
Based on a patch by José Aliste <jaliste@src.gnome.org>.
3f5d08
3f5d08
https://gitlab.freedesktop.org/poppler/poppler/issues/631
3f5d08
---
3f5d08
 glib/demo/utils.c                   |   2 -
3f5d08
 glib/poppler-action.cc              |   5 +-
3f5d08
 glib/poppler-action.h               |  16 +++
3f5d08
 glib/poppler-document.cc            | 151 ++++++++++++++++++++++++----
3f5d08
 glib/reference/poppler-sections.txt |   2 +
3f5d08
 5 files changed, 154 insertions(+), 22 deletions(-)
3f5d08
3f5d08
diff --git a/glib/demo/utils.c b/glib/demo/utils.c
3f5d08
index 6bf61614..38bde147 100644
3f5d08
--- a/glib/demo/utils.c
3f5d08
+++ b/glib/demo/utils.c
3f5d08
@@ -151,8 +151,6 @@ pgd_action_view_add_destination (GtkWidget   *action_view,
3f5d08
 		pgd_table_add_property (table, "Zoom:", str, row);
3f5d08
 		g_free (str);
3f5d08
 	} else {
3f5d08
-		pgd_table_add_property (table, "Named Dest:", dest->named_dest, row);
3f5d08
-
3f5d08
 		if (document && !remote) {
3f5d08
 			PopplerDest *new_dest;
3f5d08
 
3f5d08
diff --git a/glib/poppler-action.cc b/glib/poppler-action.cc
3f5d08
index 9af67571..7e0bc031 100644
3f5d08
--- a/glib/poppler-action.cc
3f5d08
+++ b/glib/poppler-action.cc
3f5d08
@@ -328,7 +328,8 @@ dest_new_named (const GooString *named_dest)
3f5d08
 	}
3f5d08
 
3f5d08
 	dest->type = POPPLER_DEST_NAMED;
3f5d08
-	dest->named_dest = g_strdup (named_dest->getCString ());
3f5d08
+	dest->named_dest = poppler_named_dest_from_bytestring((const guint8*)named_dest->getCString (),
3f5d08
+							      named_dest->getLength ());
3f5d08
 
3f5d08
 	return dest;
3f5d08
 }
3f5d08
diff --git a/glib/poppler-action.h b/glib/poppler-action.h
3f5d08
index 13468f79..93a026be 100644
3f5d08
--- a/glib/poppler-action.h
3f5d08
+++ b/glib/poppler-action.h
3f5d08
@@ -164,6 +164,14 @@ typedef struct _PopplerActionJavascript PopplerActionJavascript;
3f5d08
  * @change_zoom: whether scale factor should be changed
3f5d08
  *
3f5d08
  * Data structure for holding a destination
3f5d08
+ *
3f5d08
+ * Note that @named_dest is the string representation of the named
3f5d08
+ * destination. This is the right form to pass to poppler functions,
3f5d08
+ * e.g. poppler_document_find_dest(), but to get the destination as
3f5d08
+ * it appears in the PDF itself, you need to convert it to a bytestring
3f5d08
+ * with poppler_named_dest_to_bytestring() first.
3f5d08
+ * Also note that @named_dest does not have a defined encoding and
3f5d08
+ * is not in a form suitable to be displayed to the user.
3f5d08
  */
3f5d08
 struct _PopplerDest
3f5d08
 {
3f5d08
@@ -317,6 +325,12 @@ void           poppler_dest_free       (PopplerDest   *dest);
3f5d08
 void           poppler_dest_free       (PopplerDest   *dest);
3f5d08
 PopplerDest   *poppler_dest_copy       (PopplerDest   *dest);
3f5d08
 
3f5d08
+char   *poppler_named_dest_from_bytestring (const guint8 *data,
3f5d08
+                                            gsize         length);
3f5d08
+
3f5d08
+guint8 *poppler_named_dest_to_bytestring   (const char   *named_dest,
3f5d08
+                                            gsize        *length);
3f5d08
+
3f5d08
 G_END_DECLS
3f5d08
 
3f5d08
 #endif /* __POPPLER_GLIB_H__ */
3f5d08
diff --git a/glib/poppler-document.cc b/glib/poppler-document.cc
3f5d08
index a9b4103d..d97d1448 100644
3f5d08
--- a/glib/poppler-document.cc
3f5d08
+++ b/glib/poppler-document.cc
3f5d08
@@ -680,41 +680,154 @@ poppler_document_get_attachments (PopplerDocument *document)
3f5d08
   return g_list_reverse (retval);
3f5d08
 }
3f5d08
 
3f5d08
+/**
3f5d08
+ * poppler_named_dest_from_bytestring:
3f5d08
+ * @data: (array length=length): the bytestring data
3f5d08
+ * @length: the bytestring length
3f5d08
+ *
3f5d08
+ * Converts a bytestring into a zero-terminated string suitable to
3f5d08
+ * pass to poppler_document_find_dest().
3f5d08
+ *
3f5d08
+ * Note that the returned string has no defined encoding and is not
3f5d08
+ * suitable for display to the user.
3f5d08
+ *
3f5d08
+ * The returned data must be freed using g_free().
3f5d08
+ *
3f5d08
+ * Returns: (transfer full): the named dest
3f5d08
+ *
3f5d08
+ * Since: 0.73
3f5d08
+ */
3f5d08
+char *
3f5d08
+poppler_named_dest_from_bytestring (const guint8 *data,
3f5d08
+				    gsize         length)
3f5d08
+{
3f5d08
+  const guint8 *p, *pend;
3f5d08
+  char *dest, *q;
3f5d08
+
3f5d08
+  g_return_val_if_fail (length != 0 || data != NULL, NULL);
3f5d08
+  /* Each source byte needs maximally 2 destination chars (\\ or \0) */
3f5d08
+  q = dest = (gchar *)g_malloc (length * 2 + 1);
3f5d08
+
3f5d08
+  pend = data + length;
3f5d08
+  for (p = data; p < pend; ++p) {
3f5d08
+    switch (*p) {
3f5d08
+    case '\0':
3f5d08
+      *q++ = '\\';
3f5d08
+      *q++ = '0';
3f5d08
+      break;
3f5d08
+    case '\\':
3f5d08
+      *q++ = '\\';
3f5d08
+      *q++ = '\\';
3f5d08
+      break;
3f5d08
+    default:
3f5d08
+      *q++ = *p;
3f5d08
+      break;
3f5d08
+    }
3f5d08
+  }
3f5d08
+
3f5d08
+  *q = 0; /* zero terminate */
3f5d08
+  return dest;
3f5d08
+}
3f5d08
+
3f5d08
+/**
3f5d08
+ * poppler_named_dest_to_bytestring:
3f5d08
+ * @name: the named dest string
3f5d08
+ * @length: (out): a location to store the length of the returned bytestring
3f5d08
+ *
3f5d08
+ * Converts a named dest string (e.g. from #PopplerDest.named_dest) into a
3f5d08
+ * bytestring, inverting the transformation of
3f5d08
+ * poppler_named_dest_from_bytestring().
3f5d08
+ *
3f5d08
+ * Note that the returned data is not zero terminated and may also
3f5d08
+ * contains embedded NUL bytes.
3f5d08
+ *
3f5d08
+ * If @name is not a valid named dest string, returns %NULL.
3f5d08
+ *
3f5d08
+ * The returned data must be freed using g_free().
3f5d08
+ *
3f5d08
+ * Returns: (array length=length) (transfer full) (nullable): a new bytestring,
3f5d08
+ *   or %NULL
3f5d08
+ *
3f5d08
+ * Since: 0.73
3f5d08
+ */
3f5d08
+guint8 *
3f5d08
+poppler_named_dest_to_bytestring (const char *name,
3f5d08
+				  gsize      *length)
3f5d08
+{
3f5d08
+  const char *p;
3f5d08
+  guint8 *data, *q;
3f5d08
+  gsize len;
3f5d08
+
3f5d08
+  g_return_val_if_fail (name != NULL, NULL);
3f5d08
+  g_return_val_if_fail (length != NULL, NULL);
3f5d08
+
3f5d08
+  len = strlen (name);
3f5d08
+  q = data = (guint8*) g_malloc (len);
3f5d08
+  for (p = name; *p; ++p) {
3f5d08
+    if (*p == '\\') {
3f5d08
+      p++;
3f5d08
+      len--;
3f5d08
+      if (*p == '0')
3f5d08
+	*q++ = '\0';
3f5d08
+      else if (*p == '\\')
3f5d08
+	*q++ = '\\';
3f5d08
+      else
3f5d08
+	goto invalid;
3f5d08
+    } else {
3f5d08
+      *q++ = *p;
3f5d08
+    }
3f5d08
+  }
3f5d08
+
3f5d08
+  *length = len;
3f5d08
+  return data;
3f5d08
+
3f5d08
+invalid:
3f5d08
+  g_free(data);
3f5d08
+  *length = 0;
3f5d08
+  return NULL;
3f5d08
+}
3f5d08
+
3f5d08
 /**
3f5d08
  * poppler_document_find_dest:
3f5d08
  * @document: A #PopplerDocument
3f5d08
  * @link_name: a named destination
3f5d08
  *
3f5d08
- * Finds named destination @link_name in @document
3f5d08
+ * Creates a #PopplerDest for the named destination @link_name in @document.
3f5d08
+ *
3f5d08
+ * Note that named destinations are bytestrings, not string. That means that
3f5d08
+ * unless @link_name was returned by a poppler function (e.g. is
3f5d08
+ * #PopplerDest.named_dest), it needs to be converted to string
3f5d08
+ * using poppler_named_dest_from_bytestring() before being passed to this
3f5d08
+ * function.
3f5d08
  *
3f5d08
- * Return value: The #PopplerDest destination or %NULL if
3f5d08
- * @link_name is not a destination. Returned value must
3f5d08
- * be freed with #poppler_dest_free
3f5d08
+ * The returned value must be freed with poppler_dest_free().
3f5d08
+ *
3f5d08
+ * Return value: (transfer full): a new #PopplerDest destination, or %NULL if
3f5d08
+ *   @link_name is not a destination.
3f5d08
  **/
3f5d08
 PopplerDest *
3f5d08
 poppler_document_find_dest (PopplerDocument *document,
3f5d08
 			    const gchar     *link_name)
3f5d08
 {
3f5d08
-	PopplerDest *dest = NULL;
3f5d08
-	LinkDest *link_dest = NULL;
3f5d08
-	GooString *g_link_name;
3f5d08
+  g_return_val_if_fail (POPPLER_IS_DOCUMENT (document), NULL);
3f5d08
+  g_return_val_if_fail (link_name != NULL, NULL);
3f5d08
 
3f5d08
-	g_return_val_if_fail (POPPLER_IS_DOCUMENT (document), NULL);
3f5d08
-	g_return_val_if_fail (link_name != NULL, NULL);
3f5d08
+  gsize len;
3f5d08
+  guint8* data = poppler_named_dest_to_bytestring (link_name, &len;;
3f5d08
+  if (data == NULL)
3f5d08
+    return NULL;
3f5d08
 
3f5d08
-	g_link_name = new GooString (link_name);
3f5d08
+  GooString g_link_name ((const char*)data, (int)len);
3f5d08
+  g_free (data);
3f5d08
 
3f5d08
-	if (g_link_name) {
3f5d08
-		link_dest = document->doc->findDest (g_link_name);
3f5d08
-		delete g_link_name;
3f5d08
-	}
3f5d08
+  LinkDest *link_dest = document->doc->findDest (&g_link_name);
3f5d08
+  if (link_dest == NULL)
3f5d08
+    return NULL;
3f5d08
 
3f5d08
-	if (link_dest) {
3f5d08
-		dest = _poppler_dest_new_goto (document, link_dest);
3f5d08
-		delete link_dest;
3f5d08
-	}
3f5d08
+  PopplerDest *dest = _poppler_dest_new_goto (document, link_dest);
3f5d08
+  delete link_dest;
3f5d08
 
3f5d08
-	return dest;
3f5d08
+  return dest;
3f5d08
 }
3f5d08
 
3f5d08
 char *_poppler_goo_string_to_utf8(GooString *s)
3f5d08
diff --git a/glib/reference/poppler-sections.txt b/glib/reference/poppler-sections.txt
3f5d08
index 6c15f773..39985553 100644
3f5d08
--- a/glib/reference/poppler-sections.txt
3f5d08
+++ b/glib/reference/poppler-sections.txt
3f5d08
@@ -735,6 +735,8 @@ poppler_text_span_get_type
3f5d08
 poppler_get_backend
3f5d08
 poppler_get_version
3f5d08
 poppler_date_parse
3f5d08
+poppler_named_dest_from_bytestring
3f5d08
+poppler_named_dest_to_bytestring
3f5d08
 poppler_color_new
3f5d08
 poppler_color_copy
3f5d08
 poppler_color_free
3f5d08
-- 
3f5d08
2.26.2
3f5d08