1 /*
2  * Copyright (C) 2009 Jelmer Vernooij <jelmer@jelmer.uk>
3  *
4  * Dulwich is dual-licensed under the Apache License, Version 2.0 and the GNU
5  * General Public License as public by the Free Software Foundation; version 2.0
6  * or (at your option) any later version. You can redistribute it and/or
7  * modify it under the terms of either of these two licenses.
8  *
9  * Unless required by applicable law or agreed to in writing, software
10  * distributed under the License is distributed on an "AS IS" BASIS,
11  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12  * See the License for the specific language governing permissions and
13  * limitations under the License.
14  *
15  * You should have received a copy of the licenses; if not, see
16  * <http://www.gnu.org/licenses/> for a copy of the GNU General Public License
17  * and <http://www.apache.org/licenses/LICENSE-2.0> for a copy of the Apache
18  * License, Version 2.0.
19  */
20 
21 #define PY_SSIZE_T_CLEAN
22 #include <Python.h>
23 #include <stdlib.h>
24 #include <sys/stat.h>
25 
26 #if PY_MAJOR_VERSION >= 3
27 #define PyInt_Check(obj) 0
28 #define PyInt_CheckExact(obj) 0
29 #define PyInt_AsLong PyLong_AsLong
30 #define PyString_AS_STRING PyBytes_AS_STRING
31 #define PyString_Check PyBytes_Check
32 #define PyString_FromStringAndSize PyBytes_FromStringAndSize
33 #endif
34 
35 #if defined(__MINGW32_VERSION) || defined(__APPLE__)
36 size_t rep_strnlen(char *text, size_t maxlen);
rep_strnlen(char * text,size_t maxlen)37 size_t rep_strnlen(char *text, size_t maxlen)
38 {
39 	const char *last = memchr(text, '\0', maxlen);
40 	return last ? (size_t) (last - text) : maxlen;
41 }
42 #define strnlen rep_strnlen
43 #endif
44 
45 #define bytehex(x) (((x)<0xa)?('0'+(x)):('a'-0xa+(x)))
46 
47 static PyObject *tree_entry_cls;
48 static PyObject *object_format_exception_cls;
49 
sha_to_pyhex(const unsigned char * sha)50 static PyObject *sha_to_pyhex(const unsigned char *sha)
51 {
52 	char hexsha[41];
53 	int i;
54 	for (i = 0; i < 20; i++) {
55 		hexsha[i*2] = bytehex((sha[i] & 0xF0) >> 4);
56 		hexsha[i*2+1] = bytehex(sha[i] & 0x0F);
57 	}
58 
59 	return PyString_FromStringAndSize(hexsha, 40);
60 }
61 
py_parse_tree(PyObject * self,PyObject * args,PyObject * kw)62 static PyObject *py_parse_tree(PyObject *self, PyObject *args, PyObject *kw)
63 {
64 	char *text, *start, *end;
65 	Py_ssize_t len; int strict;
66 	size_t namelen;
67 	PyObject *ret, *item, *name, *sha, *py_strict = NULL;
68 	static char *kwlist[] = {"text", "strict", NULL};
69 
70 #if PY_MAJOR_VERSION >= 3
71 	if (!PyArg_ParseTupleAndKeywords(args, kw, "y#|O", kwlist,
72 	                                 &text, &len, &py_strict))
73 #else
74 	if (!PyArg_ParseTupleAndKeywords(args, kw, "s#|O", kwlist,
75 	                                 &text, &len, &py_strict))
76 #endif
77 		return NULL;
78 	strict = py_strict ?  PyObject_IsTrue(py_strict) : 0;
79 	/* TODO: currently this returns a list; if memory usage is a concern,
80 	 * consider rewriting as a custom iterator object */
81 	ret = PyList_New(0);
82 	if (ret == NULL) {
83 		return NULL;
84 	}
85 	start = text;
86 	end = text + len;
87 	while (text < end) {
88 		long mode;
89 		if (strict && text[0] == '0') {
90 			PyErr_SetString(object_format_exception_cls,
91 			                "Illegal leading zero on mode");
92 			Py_DECREF(ret);
93 			return NULL;
94 		}
95 		mode = strtol(text, &text, 8);
96 		if (*text != ' ') {
97 			PyErr_SetString(PyExc_ValueError, "Expected space");
98 			Py_DECREF(ret);
99 			return NULL;
100 		}
101 		text++;
102 		namelen = strnlen(text, len - (text - start));
103 		name = PyString_FromStringAndSize(text, namelen);
104 		if (name == NULL) {
105 			Py_DECREF(ret);
106 			return NULL;
107 		}
108 		if (text + namelen + 20 >= end) {
109 			PyErr_SetString(PyExc_ValueError, "SHA truncated");
110 			Py_DECREF(ret);
111 			Py_DECREF(name);
112 			return NULL;
113 		}
114 		sha = sha_to_pyhex((unsigned char *)text+namelen+1);
115 		if (sha == NULL) {
116 			Py_DECREF(ret);
117 			Py_DECREF(name);
118 			return NULL;
119 		}
120 		item = Py_BuildValue("(NlN)", name, mode, sha);
121 		if (item == NULL) {
122 			Py_DECREF(ret);
123 			Py_DECREF(sha);
124 			Py_DECREF(name);
125 			return NULL;
126 		}
127 		if (PyList_Append(ret, item) == -1) {
128 			Py_DECREF(ret);
129 			Py_DECREF(item);
130 			return NULL;
131 		}
132 		Py_DECREF(item);
133 		text += namelen+21;
134 	}
135 	return ret;
136 }
137 
138 struct tree_item {
139 	const char *name;
140 	int mode;
141 	PyObject *tuple;
142 };
143 
cmp_tree_item(const void * _a,const void * _b)144 int cmp_tree_item(const void *_a, const void *_b)
145 {
146 	const struct tree_item *a = _a, *b = _b;
147 	const char *remain_a, *remain_b;
148 	int ret;
149 	size_t common;
150 	if (strlen(a->name) > strlen(b->name)) {
151 		common = strlen(b->name);
152 		remain_a = a->name + common;
153 		remain_b = (S_ISDIR(b->mode)?"/":"");
154 	} else if (strlen(b->name) > strlen(a->name)) {
155 		common = strlen(a->name);
156 		remain_a = (S_ISDIR(a->mode)?"/":"");
157 		remain_b = b->name + common;
158 	} else { /* strlen(a->name) == strlen(b->name) */
159 		common = 0;
160 		remain_a = a->name;
161 		remain_b = b->name;
162 	}
163 	ret = strncmp(a->name, b->name, common);
164 	if (ret != 0)
165 		return ret;
166 	return strcmp(remain_a, remain_b);
167 }
168 
cmp_tree_item_name_order(const void * _a,const void * _b)169 int cmp_tree_item_name_order(const void *_a, const void *_b) {
170 	const struct tree_item *a = _a, *b = _b;
171 	return strcmp(a->name, b->name);
172 }
173 
py_sorted_tree_items(PyObject * self,PyObject * args)174 static PyObject *py_sorted_tree_items(PyObject *self, PyObject *args)
175 {
176 	struct tree_item *qsort_entries = NULL;
177 	int name_order, n = 0, i;
178 	PyObject *entries, *py_name_order, *ret, *key, *value, *py_mode, *py_sha;
179 	Py_ssize_t pos = 0, num_entries;
180 	int (*cmp)(const void *, const void *);
181 
182 	if (!PyArg_ParseTuple(args, "OO", &entries, &py_name_order))
183 		goto error;
184 
185 	if (!PyDict_Check(entries)) {
186 		PyErr_SetString(PyExc_TypeError, "Argument not a dictionary");
187 		goto error;
188 	}
189 
190 	name_order = PyObject_IsTrue(py_name_order);
191 	if (name_order == -1)
192 		goto error;
193 	cmp = name_order ? cmp_tree_item_name_order : cmp_tree_item;
194 
195 	num_entries = PyDict_Size(entries);
196 	if (PyErr_Occurred())
197 		goto error;
198 	qsort_entries = PyMem_New(struct tree_item, num_entries);
199 	if (!qsort_entries) {
200 		PyErr_NoMemory();
201 		goto error;
202 	}
203 
204 	while (PyDict_Next(entries, &pos, &key, &value)) {
205 		if (!PyString_Check(key)) {
206 			PyErr_SetString(PyExc_TypeError, "Name is not a string");
207 			goto error;
208 		}
209 
210 		if (PyTuple_Size(value) != 2) {
211 			PyErr_SetString(PyExc_ValueError, "Tuple has invalid size");
212 			goto error;
213 		}
214 
215 		py_mode = PyTuple_GET_ITEM(value, 0);
216 		if (!PyInt_Check(py_mode) && !PyLong_Check(py_mode)) {
217 			PyErr_SetString(PyExc_TypeError, "Mode is not an integral type");
218 			goto error;
219 		}
220 
221 		py_sha = PyTuple_GET_ITEM(value, 1);
222 		if (!PyString_Check(py_sha)) {
223 			PyErr_SetString(PyExc_TypeError, "SHA is not a string");
224 			goto error;
225 		}
226 		qsort_entries[n].name = PyString_AS_STRING(key);
227 		qsort_entries[n].mode = PyInt_AsLong(py_mode);
228 
229 		qsort_entries[n].tuple = PyObject_CallFunctionObjArgs(
230 		                tree_entry_cls, key, py_mode, py_sha, NULL);
231 		if (qsort_entries[n].tuple == NULL)
232 			goto error;
233 		n++;
234 	}
235 
236 	qsort(qsort_entries, num_entries, sizeof(struct tree_item), cmp);
237 
238 	ret = PyList_New(num_entries);
239 	if (ret == NULL) {
240 		PyErr_NoMemory();
241 		goto error;
242 	}
243 
244 	for (i = 0; i < num_entries; i++) {
245 		PyList_SET_ITEM(ret, i, qsort_entries[i].tuple);
246 	}
247 	PyMem_Free(qsort_entries);
248 	return ret;
249 
250 error:
251 	for (i = 0; i < n; i++) {
252 		Py_XDECREF(qsort_entries[i].tuple);
253 	}
254 	PyMem_Free(qsort_entries);
255 	return NULL;
256 }
257 
258 static PyMethodDef py_objects_methods[] = {
259 	{ "parse_tree", (PyCFunction)py_parse_tree, METH_VARARGS | METH_KEYWORDS,
260 	  NULL },
261 	{ "sorted_tree_items", py_sorted_tree_items, METH_VARARGS, NULL },
262 	{ NULL, NULL, 0, NULL }
263 };
264 
265 static PyObject *
moduleinit(void)266 moduleinit(void)
267 {
268 	PyObject *m, *objects_mod, *errors_mod;
269 
270 #if PY_MAJOR_VERSION >= 3
271 	static struct PyModuleDef moduledef = {
272 		PyModuleDef_HEAD_INIT,
273 		"_objects",         /* m_name */
274 		NULL,               /* m_doc */
275 		-1,                 /* m_size */
276 		py_objects_methods, /* m_methods */
277 		NULL,               /* m_reload */
278 		NULL,               /* m_traverse */
279 		NULL,               /* m_clear*/
280 		NULL,               /* m_free */
281 	};
282 	m = PyModule_Create(&moduledef);
283 #else
284 	m = Py_InitModule3("_objects", py_objects_methods, NULL);
285 #endif
286 	if (m == NULL) {
287 		return NULL;
288 	}
289 
290 	errors_mod = PyImport_ImportModule("dulwich.errors");
291 	if (errors_mod == NULL) {
292 		return NULL;
293 	}
294 
295 	object_format_exception_cls = PyObject_GetAttrString(
296 		errors_mod, "ObjectFormatException");
297 	Py_DECREF(errors_mod);
298 	if (object_format_exception_cls == NULL) {
299 		return NULL;
300 	}
301 
302 	/* This is a circular import but should be safe since this module is
303 	 * imported at at the very bottom of objects.py. */
304 	objects_mod = PyImport_ImportModule("dulwich.objects");
305 	if (objects_mod == NULL) {
306 		return NULL;
307 	}
308 
309 	tree_entry_cls = PyObject_GetAttrString(objects_mod, "TreeEntry");
310 	Py_DECREF(objects_mod);
311 	if (tree_entry_cls == NULL) {
312 		return NULL;
313 	}
314 
315 	return m;
316 }
317 
318 #if PY_MAJOR_VERSION >= 3
319 PyMODINIT_FUNC
PyInit__objects(void)320 PyInit__objects(void)
321 {
322 	return moduleinit();
323 }
324 #else
325 PyMODINIT_FUNC
init_objects(void)326 init_objects(void)
327 {
328 	moduleinit();
329 }
330 #endif
331