From 1c9e36639b949cc765dab316825f9fec7af85a6e Mon Sep 17 00:00:00 2001
From: Cyrille Bagard <nocbos@gmail.com>
Date: Tue, 1 Dec 2015 21:11:27 +0100
Subject: Computed relative and absolute paths.

---
 ChangeLog                           |  29 ++++++
 plugins/pychrysa/common/Makefile.am |   3 +-
 plugins/pychrysa/common/fnv1a.c     |   2 +-
 plugins/pychrysa/common/module.c    |   2 +
 plugins/pychrysa/common/pathname.c  | 203 ++++++++++++++++++++++++++++++++++++
 plugins/pychrysa/common/pathname.h  |  42 ++++++++
 src/common/Makefile.am              |   1 +
 src/common/extstr.h                 |   2 +-
 src/common/pathname.c               | 173 ++++++++++++++++++++++++++++++
 src/common/pathname.h               |  36 +++++++
 tests/common/__init__.py            |   0
 tests/common/pathname.py            |  95 +++++++++++++++++
 12 files changed, 585 insertions(+), 3 deletions(-)
 create mode 100644 plugins/pychrysa/common/pathname.c
 create mode 100644 plugins/pychrysa/common/pathname.h
 create mode 100644 src/common/pathname.c
 create mode 100644 src/common/pathname.h
 create mode 100644 tests/common/__init__.py
 create mode 100644 tests/common/pathname.py

diff --git a/ChangeLog b/ChangeLog
index 57bbdb9..a06eeaf 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,32 @@
+15-12-01  Cyrille Bagard <nocbos@gmail.com>
+
+	* plugins/pychrysa/common/Makefile.am:
+	Add the 'pathname.[ch]' files to libpychrysacommon_la_SOURCES.
+
+	* plugins/pychrysa/common/fnv1a.c:
+	Fix comments.
+
+	* plugins/pychrysa/common/module.c:
+	Update code.
+
+	* plugins/pychrysa/common/pathname.c:
+	* plugins/pychrysa/common/pathname.h:
+	New entries: provide bindings for Python.
+
+	* src/common/Makefile.am:
+	Add the 'pathname.[ch]' files to libcommon_la_SOURCES.
+
+	* src/common/extstr.h:
+	Typo.
+
+	* src/common/pathname.c:
+	* src/common/pathname.h:
+	New entries: compute relative and absolute paths.
+
+	* tests/common/__init__.py:
+	* tests/common/pathname.py:
+	New entries: define some new relative test cases.
+
 15-11-29  Cyrille Bagard <nocbos@gmail.com>
 
 	* configure.ac:
diff --git a/plugins/pychrysa/common/Makefile.am b/plugins/pychrysa/common/Makefile.am
index 38d1697..93d92ac 100644
--- a/plugins/pychrysa/common/Makefile.am
+++ b/plugins/pychrysa/common/Makefile.am
@@ -3,7 +3,8 @@ noinst_LTLIBRARIES = libpychrysacommon.la
 
 libpychrysacommon_la_SOURCES =			\
 	fnv1a.h fnv1a.c						\
-	module.h module.c
+	module.h module.c					\
+	pathname.h pathname.c
 
 
 libpychrysacommon_la_LDFLAGS = 
diff --git a/plugins/pychrysa/common/fnv1a.c b/plugins/pychrysa/common/fnv1a.c
index 2befcbe..99d06fc 100644
--- a/plugins/pychrysa/common/fnv1a.c
+++ b/plugins/pychrysa/common/fnv1a.c
@@ -40,7 +40,7 @@ static PyObject *py_fnv1a_hash(PyObject *, PyObject *);
 /******************************************************************************
 *                                                                             *
 *  Paramètres  : self = NULL car méthode statique.                            *
-*                args = non utilisé ici.                                      *
+*                args = arguments fournis lors de l'appel à la fonction.      *
 *                                                                             *
 *  Description : Détermine l'empreinte FNV1a d'une chaîne de caractères.      *
 *                                                                             *
diff --git a/plugins/pychrysa/common/module.c b/plugins/pychrysa/common/module.c
index bb4d47f..21781b5 100644
--- a/plugins/pychrysa/common/module.c
+++ b/plugins/pychrysa/common/module.c
@@ -26,6 +26,7 @@
 
 
 #include "fnv1a.h"
+#include "pathname.h"
 
 
 
@@ -76,6 +77,7 @@ bool add_common_module_to_python_module(PyObject *super)
     result = true;
 
     result &= register_python_fnv1a(module);
+    result &= register_python_pathname(module);
 
  acmtpm_exit:
 
diff --git a/plugins/pychrysa/common/pathname.c b/plugins/pychrysa/common/pathname.c
new file mode 100644
index 0000000..4b36675
--- /dev/null
+++ b/plugins/pychrysa/common/pathname.c
@@ -0,0 +1,203 @@
+
+/* Chrysalide - Outil d'analyse de fichiers binaires
+ * pathname.c - équivalent Python du fichier "common/pathname.c"
+ *
+ * Copyright (C) 2015 Cyrille Bagard
+ *
+ *  This file is part of Chrysalide.
+ *
+ *  OpenIDA is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  OpenIDA is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+
+#include "pathname.h"
+
+
+#include <malloc.h>
+#include <pygobject.h>
+
+
+#include <i18n.h>
+
+
+#include <common/pathname.h>
+
+
+
+/* Calcule le chemin relatif entre deux fichiers donnés. */
+static PyObject *py_build_relative_filename(PyObject *, PyObject *);
+
+/* Calcule le chemin absolu d'un fichier par rapport à un autre. */
+static PyObject *py_build_absolute_filename(PyObject *, PyObject *);
+
+
+
+/******************************************************************************
+*                                                                             *
+*  Paramètres  : self = NULL car méthode statique.                            *
+*                args = arguments fournis lors de l'appel à la fonction.      *
+*                                                                             *
+*  Description : Calcule le chemin relatif entre deux fichiers donnés.        *
+*                                                                             *
+*  Retour      : Chemin relatif obtenu.                                       *
+*                                                                             *
+*  Remarques   : -                                                            *
+*                                                                             *
+******************************************************************************/
+
+static PyObject *py_build_relative_filename(PyObject *self, PyObject *args)
+{
+    PyObject *result;                       /* Instance à retourner        */
+    const char *ref;                        /* Fichier de référence        */
+    const char *target;                     /* Fichier à cibler            */
+    int ret;                                /* Bilan de lecture des args.  */
+    char *relative;                         /* Chemin d'accès construit    */
+
+    ret = PyArg_ParseTuple(args, "ss", &ref, &target);
+    if (!ret) Py_RETURN_NONE;
+
+    relative = build_relative_filename(ref, target);
+
+    result = PyUnicode_FromString(relative);
+
+    free(relative);
+
+    return result;
+
+}
+
+
+/******************************************************************************
+*                                                                             *
+*  Paramètres  : self = NULL car méthode statique.                            *
+*                args = arguments fournis lors de l'appel à la fonction.      *
+*                                                                             *
+*  Description : Calcule le chemin absolu d'un fichier par rapport à un autre.*
+*                                                                             *
+*  Retour      : Chemin absolu obtenu.                                        *
+*                                                                             *
+*  Remarques   : -                                                            *
+*                                                                             *
+******************************************************************************/
+
+static PyObject *py_build_absolute_filename(PyObject *self, PyObject *args)
+{
+    PyObject *result;                       /* Instance à retourner        */
+    const char *ref;                        /* Fichier de référence        */
+    const char *target;                     /* Fichier à cibler            */
+    int ret;                                /* Bilan de lecture des args.  */
+    char *relative;                         /* Chemin d'accès construit    */
+
+    ret = PyArg_ParseTuple(args, "ss", &ref, &target);
+    if (!ret) Py_RETURN_NONE;
+
+    relative = build_absolute_filename(ref, target);
+
+    if (relative == NULL)
+    {
+        PyErr_SetString(PyExc_ValueError, _("Relative path is too deep."));
+        result = NULL;
+    }
+    else
+    {
+        result = PyUnicode_FromString(relative);
+
+        free(relative);
+
+    }
+
+    return result;
+
+}
+
+
+/******************************************************************************
+*                                                                             *
+*  Paramètres  : -                                                            *
+*                                                                             *
+*  Description : Fournit un accès à une définition de type à diffuser.        *
+*                                                                             *
+*  Retour      : Définition d'objet pour Python.                              *
+*                                                                             *
+*  Remarques   : -                                                            *
+*                                                                             *
+******************************************************************************/
+
+PyTypeObject *get_python_pathname_type(void)
+{
+    static PyMethodDef py_pathname_methods[] = {
+
+        { "build_relative_filename", py_build_relative_filename,
+          METH_VARARGS | METH_STATIC,
+          "build_relative_filename(ref, target, /)\n--\n\nCompute the relative path between two files."
+        },
+        { "build_absolute_filename", py_build_absolute_filename,
+          METH_VARARGS | METH_STATIC,
+          "build_absolute_filename(ref, target, /)\n--\n\nCompute the absolute path for a file."
+        },
+        { NULL }
+
+    };
+
+    static PyTypeObject py_pathname_type = {
+
+        PyVarObject_HEAD_INIT(NULL, 0)
+
+        .tp_name = "pychrysalide.core.pathname",
+        .tp_basicsize = sizeof(PyObject),
+
+        .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_IS_ABSTRACT,
+
+        .tp_doc = "Path manipulations in Python for Chrysalide.",
+
+        .tp_methods =  py_pathname_methods
+
+    };
+
+    return &py_pathname_type;
+
+}
+
+
+/******************************************************************************
+*                                                                             *
+*  Paramètres  : module = module dont la définition est à compléter.          *
+*                                                                             *
+*  Description : Prend en charge l'objet 'pychrysalide.core.pathname'.        *
+*                                                                             *
+*  Retour      : Bilan de l'opération.                                        *
+*                                                                             *
+*  Remarques   : -                                                            *
+*                                                                             *
+******************************************************************************/
+
+bool register_python_pathname(PyObject *module)
+{
+    PyTypeObject *py_pathname_type;         /* Type Python pour 'pathname' */
+    int ret;                                /* Bilan d'un appel            */
+
+    py_pathname_type = get_python_pathname_type();
+
+    //py_pathname_type->tp_new = PyType_GenericNew;
+
+    if (PyType_Ready(py_pathname_type) != 0)
+        return false;
+
+    Py_INCREF(py_pathname_type);
+    ret = PyModule_AddObject(module, "pathname", (PyObject *)py_pathname_type);
+
+    return (ret == 0);
+
+}
diff --git a/plugins/pychrysa/common/pathname.h b/plugins/pychrysa/common/pathname.h
new file mode 100644
index 0000000..fe9bc1b
--- /dev/null
+++ b/plugins/pychrysa/common/pathname.h
@@ -0,0 +1,42 @@
+
+/* Chrysalide - Outil d'analyse de fichiers binaires
+ * pathname.h - prototypes pour l'équivalent Python du fichier "common/pathname.c"
+ *
+ * Copyright (C) 2015 Cyrille Bagard
+ *
+ *  This file is part of Chrysalide.
+ *
+ *  OpenIDA is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  OpenIDA is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+
+#ifndef _PLUGINS_PYCHRYSALIDE_COMMON_PATHNAME_H
+#define _PLUGINS_PYCHRYSALIDE_COMMON_PATHNAME_H
+
+
+#include <Python.h>
+#include <stdbool.h>
+
+
+
+/* Fournit un accès à une définition de type à diffuser. */
+PyTypeObject *get_python_pathname_type(void);
+
+/* Prend en charge l'objet 'pychrysalide.common.pathname'. */
+bool register_python_pathname(PyObject *);
+
+
+
+#endif  /* _PLUGINS_PYCHRYSALIDE_COMMON_PATHNAME_H */
diff --git a/src/common/Makefile.am b/src/common/Makefile.am
index 7a35813..4c9c2a1 100755
--- a/src/common/Makefile.am
+++ b/src/common/Makefile.am
@@ -15,6 +15,7 @@ libcommon_la_SOURCES =					\
 	leb128.h leb128.c					\
 	macros.h							\
 	net.h net.c							\
+	pathname.h pathname.c				\
 	sqlite.h sqlite.c					\
 	xdg.h xdg.c							\
 	xml.h xml.c
diff --git a/src/common/extstr.h b/src/common/extstr.h
index b8bd225..6542d2f 100644
--- a/src/common/extstr.h
+++ b/src/common/extstr.h
@@ -30,7 +30,7 @@
 
 
 /* Complète une chaîne de caractères avec une autre. */
-char *stradd(char *str1, const char *str2);
+char *stradd(char *, const char *);
 
 /* Complète une chaîne de caractères avec une autre. */
 char *strnadd(char *, const char *, size_t);
diff --git a/src/common/pathname.c b/src/common/pathname.c
new file mode 100644
index 0000000..181fd1f
--- /dev/null
+++ b/src/common/pathname.c
@@ -0,0 +1,173 @@
+
+/* Chrysalide - Outil d'analyse de fichiers binaires
+ * pathname.c - manipulation de chemins de fichiers
+ *
+ * Copyright (C) 2015 Cyrille Bagard
+ *
+ *  This file is part of Chrysalide.
+ *
+ *  OpenIDA is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  OpenIDA is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with Foobar.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#include "pathname.h"
+
+
+#include <assert.h>
+#include <glib.h>
+#include <malloc.h>
+#include <string.h>
+
+
+#include "extstr.h"
+
+
+
+/******************************************************************************
+*                                                                             *
+*  Paramètres  : ref    = fichier servant de référence aux calculs.           *
+*                target = fichier absolu ciblé par la procédure.              *
+*                                                                             *
+*  Description : Calcule le chemin relatif entre deux fichiers donnés.        *
+*                                                                             *
+*  Retour      : Chemin d'accès déterminé.                                    *
+*                                                                             *
+*  Remarques   : Les chemins de type 'a//b' ne sont pas supportés.            *
+*                                                                             *
+******************************************************************************/
+
+char *build_relative_filename(const char *ref, const char *target)
+{
+    char *result;                           /* Chemin à retourner          */
+    size_t common;                          /* Taille de la partie commune */
+    const char *found;                      /* Séparateur suivant rencontré*/
+    size_t ref_next;                        /* Prochain séparateur #1      */
+    size_t target_next;                     /* Prochain séparateur #2      */
+    int ret;                                /* Bilan d'un appel            */
+    unsigned int levels;                    /* Niveaux de décalage         */
+    unsigned int i;                         /* Boucle de parcours #1       */
+
+    common = 0;
+
+    /* Recherche d'une base commune */
+
+    while (1)
+    {
+        found = strchr(ref + common, G_DIR_SEPARATOR);
+        if (found == NULL) break;
+
+        ref_next = found - ref;
+
+        found = strchr(target + common, G_DIR_SEPARATOR);
+        if (found == NULL) break;
+
+        target_next = found - target;
+
+        /* Comparaison rapide sur la longeur du nom */
+        if (ref_next != target_next) break;
+
+        /* Comparaison sur une portion de chemin */
+        ret = strncmp(ref + common, target + common, ref_next - common);
+        if (ret != 0) break;
+
+        common = ref_next + 1;
+
+    }
+
+    /* Décompte du décalage entre la référence et la cible */
+
+    found = ref + common;
+
+    for (levels = 0; ; levels++)
+    {
+        found = strchr(found, G_DIR_SEPARATOR);
+        if (found == NULL) break;
+
+        found++;
+
+    }
+
+    /* Construction du résultat final */
+
+    result = strdup(target + common);
+
+    for (i = 0; i < levels; i++)
+    {
+        result = strprep(result, G_DIR_SEPARATOR_S);
+        result = strprep(result, "..");
+    }
+
+    return result;
+
+}
+
+
+/******************************************************************************
+*                                                                             *
+*  Paramètres  : ref    = fichier servant de référence aux calculs.           *
+*                target = fichier relatif ciblé par la procédure.             *
+*                                                                             *
+*  Description : Calcule le chemin absolu d'un fichier par rapport à un autre.*
+*                                                                             *
+*  Retour      : Chemin d'accès déterminé ou NULL en cas d'erreur.            *
+*                                                                             *
+*  Remarques   : Les chemins de type 'a//b' ne sont pas supportés.            *
+*                                                                             *
+******************************************************************************/
+
+char *build_absolute_filename(const char *ref, const char *target)
+{
+    char *result;                           /* Chemin à retourner          */
+    char *last_sep;                         /* Dernier séparateur trouvé   */
+    const char *target_base;                /* Base de la relativité       */
+
+    static const char upper[4] = { '.', '.', G_DIR_SEPARATOR, '\0' };
+
+    result = strdup(ref);
+
+    last_sep = strrchr(result, G_DIR_SEPARATOR);
+    assert(last_sep != NULL);
+
+    target_base = target;
+
+    /* Remontée des répertoires */
+    while (1)
+    {
+        if (strncmp(target_base, upper, 3) != 0)
+            break;
+
+        target_base += 3;
+
+        *last_sep = '\0';
+        last_sep = strrchr(result, G_DIR_SEPARATOR);
+
+        /* S'il devient impossible de remonter autant... */
+        if (last_sep == NULL) break;
+
+    }
+
+    if (last_sep == NULL)
+    {
+        free(result);
+        result = NULL;
+    }
+    else
+    {
+        *(last_sep + 1) = '\0';
+        result = stradd(result, target_base);
+    }
+
+    return result;
+
+}
diff --git a/src/common/pathname.h b/src/common/pathname.h
new file mode 100644
index 0000000..744a11b
--- /dev/null
+++ b/src/common/pathname.h
@@ -0,0 +1,36 @@
+
+/* Chrysalide - Outil d'analyse de fichiers binaires
+ * pathname.h - prototypes pour la manipulation de chemins de fichiers
+ *
+ * Copyright (C) 2015 Cyrille Bagard
+ *
+ *  This file is part of Chrysalide.
+ *
+ *  OpenIDA is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  OpenIDA is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with Foobar.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#ifndef _COMMON_PATHNAME_H
+#define _COMMON_PATHNAME_H
+
+
+/* Calcule le chemin relatif entre deux fichiers donnés. */
+char *build_relative_filename(const char *, const char *);
+
+/* Calcule le chemin absolu d'un fichier par rapport à un autre. */
+char *build_absolute_filename(const char *, const char *);
+
+
+
+#endif  /* _COMMON_PATHNAME_H */
diff --git a/tests/common/__init__.py b/tests/common/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/tests/common/pathname.py b/tests/common/pathname.py
new file mode 100644
index 0000000..5cd238c
--- /dev/null
+++ b/tests/common/pathname.py
@@ -0,0 +1,95 @@
+#!/usr/bin/python3-dbg
+# -*- coding: utf-8 -*-
+
+
+# Tests minimalistes pour valider la construction de chemins relatifs et absolus.
+
+
+from chrysacase import ChrysalideTestCase
+from pychrysalide.common import pathname
+
+
+class TestRestrictedContent(ChrysalideTestCase):
+    """TestCase for common.pathname.build*"""
+
+    @classmethod
+    def setUpClass(cls):
+
+        super(TestRestrictedContent, cls).setUpClass()
+
+        cls._tests = [
+            {
+                'ref'      : '/a/b/d',
+                'target'   : '/a/b/e/f',
+                'relative' : 'e/f'
+            },
+            {
+                'ref'      : '/a/b/c/d',
+                'target'   : '/a/b/f',
+                'relative' : '../f'
+            },
+            {
+                'ref'      : '/a/b/c/d',
+                'target'   : '/a/b/c/e',
+                'relative' : 'e'
+            },
+            {
+                'ref'      : '/a/bb/c/d',
+                'target'   : '/a/b/e/f',
+                'relative' : '../../b/e/f'
+            },
+            {
+                'ref'      : '/a/b/c/d',
+                'target'   : '/a/bb/e/f',
+                'relative' : '../../bb/e/f'
+            },
+            {
+                'ref'      : '/a/b/c/d',
+                'target'   : '/f',
+                'relative' : '../../../f'
+            },
+            {
+                'ref'      : '/z/b/c/d',
+                'target'   : '/a/b/e/f',
+                'relative' : '../../../a/b/e/f'
+            },
+            {
+                'ref'      : '/a/bbb/c/d',
+                'target'   : '/a/bbc/e/f',
+                'relative' : '../../bbc/e/f'
+            }
+        ]
+
+
+    def testBuildingRelative(self):
+        """Build valid relative paths."""
+
+        build_relative = pathname.build_relative_filename
+
+        for tst in self._tests:
+
+            got = build_relative(tst['ref'], tst['target'])
+
+            self.assertEqual(got, tst['relative'])
+
+
+    def testBuildingAbsolute(self):
+        """Build valid absolute paths."""
+
+        build_absolute = pathname.build_absolute_filename
+
+        for tst in self._tests:
+
+            got = build_absolute(tst['ref'], tst['relative'])
+
+            self.assertEqual(got, tst['target'])
+
+
+    def testBuildingWrongAbsolute(self):
+        """Build invalid absolute paths."""
+
+        build_absolute = pathname.build_absolute_filename
+
+        with self.assertRaisesRegex(Exception, 'Relative path is too deep.'):
+
+            got = build_absolute('/a/b', '../../c')
-- 
cgit v0.11.2-87-g4458