Ensure we don't run past the end of the tree text.
[jelmer/dulwich-libgit2.git] / dulwich / _objects.c
index 121cec9fc33d9771c3216bb55b2e877dfad9a844..fef82e78f791f35f5cbc9feecab8eea7584d7715 100644 (file)
  */
 
 #include <Python.h>
+#include <stdlib.h>
+#include <sys/stat.h>
 
-#define hexbyte(x) (isdigit(x)?(x)-'0':(x)-'a'+0xa)
 #define bytehex(x) (((x)<0xa)?('0'+(x)):('a'-0xa+(x)))
 
-static PyObject *py_hex_to_sha(PyObject *self, PyObject *py_hexsha)
-{
-       char *hexsha;
-       char sha[20];
-       int i;
-
-       if (!PyString_Check(py_hexsha)) {
-               PyErr_SetString(PyExc_TypeError, "hex sha is not a string");
-               return NULL;
-       }
-
-       if (PyString_Size(py_hexsha) != 40) {
-               PyErr_SetString(PyExc_ValueError, "hex sha is not 40 bytes long");
-               return NULL;
-       }
-
-       hexsha = PyString_AsString(py_hexsha);
-
-       for (i = 0; i < 20; i++) {
-               sha[i] = (hexbyte(hexsha[i*2]) << 4) + hexbyte(hexsha[i*2+1]);
-       }
-
-       return PyString_FromStringAndSize(sha, 20);
-}
-
 static PyObject *sha_to_pyhex(const unsigned char *sha)
 {
        char hexsha[41];
@@ -59,39 +35,28 @@ static PyObject *sha_to_pyhex(const unsigned char *sha)
        return PyString_FromStringAndSize(hexsha, 40);
 }
 
-static PyObject *py_sha_to_hex(PyObject *self, PyObject *py_sha)
-{
-       if (!PyString_Check(py_sha)) {
-               PyErr_SetString(PyExc_TypeError, "sha is not a string");
-               return NULL;
-       }
-
-       if (PyString_Size(py_sha) != 20) {
-               PyErr_SetString(PyExc_ValueError, "sha is not 20 bytes long");
-               return NULL;
-       }
-
-       return sha_to_pyhex((unsigned char *)PyString_AsString(py_sha));
-}
-
 static PyObject *py_parse_tree(PyObject *self, PyObject *args)
 {
-       char *text, *end;
+       char *text, *start, *end;
        int len, namelen;
-       PyObject *ret, *item;
+       PyObject *ret, *item, *name;
 
        if (!PyArg_ParseTuple(args, "s#", &text, &len))
                return NULL;
 
+       /* TODO: currently this returns a list; if memory usage is a concern,
+       * consider rewriting as a custom iterator object */
        ret = PyList_New(0);
+
        if (ret == NULL) {
                return NULL;
        }
 
+       start = text;
        end = text + len;
 
-    while (text < end) {
-        long mode;
+       while (text < end) {
+               long mode;
                mode = strtol(text, &text, 8);
 
                if (*text != ' ') {
@@ -102,26 +67,141 @@ static PyObject *py_parse_tree(PyObject *self, PyObject *args)
 
                text++;
 
-        namelen = strlen(text);
+               namelen = strnlen(text, len - (text - start));
+
+               name = PyString_FromStringAndSize(text, namelen);
+               if (name == NULL) {
+                       Py_DECREF(ret);
+                       return NULL;
+               }
+
+               if (text + namelen + 20 >= end) {
+                       PyErr_SetString(PyExc_RuntimeError, "SHA truncated");
+                       Py_DECREF(ret);
+                       Py_DECREF(name);
+                       return NULL;
+               }
 
-        item = Py_BuildValue("(ls#N)", mode, text, namelen, 
-                                                        sha_to_pyhex(text+namelen+1));
-        if (item == NULL) {
-            Py_DECREF(ret);
-            return NULL;
-        }
-        PyList_Append(ret, item);
+               item = Py_BuildValue("(NlN)", name, mode,
+                                                        sha_to_pyhex((unsigned char *)text+namelen+1));
+               if (item == NULL) {
+                       Py_DECREF(ret);
+                       Py_DECREF(name);
+                       return NULL;
+               }
+               if (PyList_Append(ret, item) == -1) {
+                       Py_DECREF(ret);
+                       Py_DECREF(item);
+                       return NULL;
+               }
+               Py_DECREF(item);
 
                text += namelen+21;
-    }
+       }
+
+       return ret;
+}
+
+struct tree_item {
+       const char *name;
+       int mode;
+       PyObject *tuple;
+};
+
+int cmp_tree_item(const void *_a, const void *_b)
+{
+       const struct tree_item *a = _a, *b = _b;
+       const char *remain_a, *remain_b;
+       int ret, common;
+       if (strlen(a->name) > strlen(b->name)) {
+               common = strlen(b->name);
+               remain_a = a->name + common;
+               remain_b = (S_ISDIR(b->mode)?"/":"");
+       } else if (strlen(b->name) > strlen(a->name)) { 
+               common = strlen(a->name);
+               remain_a = (S_ISDIR(a->mode)?"/":"");
+               remain_b = b->name + common;
+       } else { /* strlen(a->name) == strlen(b->name) */
+               common = 0;
+               remain_a = a->name;
+               remain_b = b->name;
+       }
+       ret = strncmp(a->name, b->name, common);
+       if (ret != 0)
+               return ret;
+       return strcmp(remain_a, remain_b);
+}
+
+static PyObject *py_sorted_tree_items(PyObject *self, PyObject *entries)
+{
+       struct tree_item *qsort_entries;
+       int num, i;
+       PyObject *ret;
+       Py_ssize_t pos = 0; 
+       PyObject *key, *value;
+
+       if (!PyDict_Check(entries)) {
+               PyErr_SetString(PyExc_TypeError, "Argument not a dictionary");
+               return NULL;
+       }
+
+       num = PyDict_Size(entries);
+       qsort_entries = malloc(num * sizeof(struct tree_item));
+       if (qsort_entries == NULL) {
+               PyErr_NoMemory();
+               return NULL;
+       }
+
+       i = 0;
+       while (PyDict_Next(entries, &pos, &key, &value)) {
+               PyObject *py_mode, *py_sha;
+               
+               if (PyTuple_Size(value) != 2) {
+                       PyErr_SetString(PyExc_ValueError, "Tuple has invalid size");
+                       free(qsort_entries);
+                       return NULL;
+               }
+
+               py_mode = PyTuple_GET_ITEM(value, 0);
+               py_sha = PyTuple_GET_ITEM(value, 1);
+               qsort_entries[i].tuple = Py_BuildValue("(OOO)", key, py_mode, py_sha);
+               if (!PyString_CheckExact(key)) {
+                       PyErr_SetString(PyExc_TypeError, "Name is not a string");
+                       free(qsort_entries);
+                       return NULL;
+               }
+               qsort_entries[i].name = PyString_AS_STRING(key);
+               if (!PyInt_CheckExact(py_mode)) {
+                       PyErr_SetString(PyExc_TypeError, "Mode is not an int");
+                       free(qsort_entries);
+                       return NULL;
+               }
+               qsort_entries[i].mode = PyInt_AS_LONG(py_mode);
+               i++;
+       }
+
+       qsort(qsort_entries, num, sizeof(struct tree_item), cmp_tree_item);
+
+       ret = PyList_New(num);
+       if (ret == NULL) {
+               free(qsort_entries);
+               PyErr_NoMemory();
+               return NULL;
+       }
+
+       for (i = 0; i < num; i++) {
+               PyList_SET_ITEM(ret, i, qsort_entries[i].tuple);
+       }
+
+       free(qsort_entries);
 
-    return ret;
+       return ret;
 }
 
 static PyMethodDef py_objects_methods[] = {
-       { "hex_to_sha", (PyCFunction)py_hex_to_sha, METH_O, NULL },
-       { "sha_to_hex", (PyCFunction)py_sha_to_hex, METH_O, NULL },
-       { "parse_tree", (PyCFunction)py_parse_tree, METH_VARARGS, NULL, },
+       { "parse_tree", (PyCFunction)py_parse_tree, METH_VARARGS, NULL },
+       { "sorted_tree_items", (PyCFunction)py_sorted_tree_items, METH_O, NULL },
+       { NULL, NULL, 0, NULL }
 };
 
 void init_objects(void)