1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /*  GMime
3  *  Copyright (C) 2000-2014 Jeffrey Stedfast
4  *
5  *  This library is free software; you can redistribute it and/or
6  *  modify it under the terms of the GNU Lesser General Public License
7  *  as published by the Free Software Foundation; either version 2.1
8  *  of the License, or (at your option) any later version.
9  *
10  *  This library is distributed in the hope that it will be useful,
11  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
12  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  *  Lesser General Public License for more details.
14  *
15  *  You should have received a copy of the GNU Lesser General Public
16  *  License along with this library; if not, write to the Free
17  *  Software Foundation, 51 Franklin Street, Fifth Floor, Boston, MA
18  *  02110-1301, USA.
19  */
20 
21 
22 #ifdef HAVE_CONFIG_H
23 #include <config.h>
24 #endif
25 
26 #include <stdlib.h>
27 #include <string.h>
28 #include <errno.h>
29 
30 #include "gmime-part-iter.h"
31 #include "gmime-message-part.h"
32 #include "gmime-multipart.h"
33 #include "gmime-message.h"
34 #include "gmime-part.h"
35 
36 /**
37  * SECTION: gmime-part-iter
38  * @title: GMimePartIter
39  * @short_description: MIME part iterators
40  * @see_also: #GMimeObject
41  *
42  * #GMimePartIter is an iterator for traversing a #GMimeObject tree in
43  * Depth-First order.
44  **/
45 
46 
47 typedef struct _GMimeObjectStack GMimeObjectStack;
48 
49 struct _GMimeObjectStack {
50 	GMimeObjectStack *parent;
51 	GMimeObject *object;
52 	gboolean indexed;
53 };
54 
55 struct _GMimePartIter {
56 	GMimeObjectStack *parent;
57 	GMimeObject *toplevel;
58 	GMimeObject *current;
59 	GArray *path;
60 	int index;
61 };
62 
63 static void
g_mime_part_iter_push(GMimePartIter * iter,GMimeObject * object,int index)64 g_mime_part_iter_push (GMimePartIter *iter, GMimeObject *object, int index)
65 {
66 	GMimeObjectStack *node;
67 
68 	if (index != -1)
69 		g_array_append_val (iter->path, index);
70 
71 	node = g_slice_new (GMimeObjectStack);
72 	node->indexed = index != -1;
73 	node->parent = iter->parent;
74 	node->object = object;
75 	iter->parent = node;
76 }
77 
78 static gboolean
g_mime_part_iter_pop(GMimePartIter * iter)79 g_mime_part_iter_pop (GMimePartIter *iter)
80 {
81 	GMimeObjectStack *node;
82 
83 	if (!iter->parent || !iter->parent->parent)
84 		return FALSE;
85 
86 	if (iter->parent->indexed) {
87 		iter->index = g_array_index (iter->path, int, iter->path->len - 1);
88 		g_array_set_size (iter->path, iter->path->len - 1);
89 	}
90 
91 	iter->current = iter->parent->object;
92 
93 	node = iter->parent;
94 	iter->parent = node->parent;
95 	g_slice_free (GMimeObjectStack, node);
96 
97 	return TRUE;
98 }
99 
100 
101 /**
102  * g_mime_part_iter_new:
103  * @toplevel: a #GMimeObject to use as the toplevel
104  *
105  * Creates a new #GMimePartIter for iterating over @toplevel's subparts.
106  *
107  * Returns: a newly allocated #GMimePartIter which should be freed
108  * using g_mime_part_iter_free() when finished with it.
109  **/
110 GMimePartIter *
g_mime_part_iter_new(GMimeObject * toplevel)111 g_mime_part_iter_new (GMimeObject *toplevel)
112 {
113 	GMimePartIter *iter;
114 
115 	g_return_val_if_fail (GMIME_IS_OBJECT (toplevel), NULL);
116 
117 	iter = g_slice_new (GMimePartIter);
118 	iter->path = g_array_new (FALSE, FALSE, sizeof (int));
119 	iter->toplevel = toplevel;
120 	g_object_ref (toplevel);
121 	iter->parent = NULL;
122 
123 	g_mime_part_iter_reset (iter);
124 
125 	return iter;
126 }
127 
128 
129 /**
130  * g_mime_part_iter_free:
131  * @iter: a #GMimePartIter
132  *
133  * Frees the memory allocated by g_mime_part_iter_new().
134  **/
135 void
g_mime_part_iter_free(GMimePartIter * iter)136 g_mime_part_iter_free (GMimePartIter *iter)
137 {
138 	if (iter == NULL)
139 		return;
140 
141 	g_object_unref (iter->toplevel);
142 	g_array_free (iter->path, TRUE);
143 	if (iter->parent != NULL)
144 		g_slice_free_chain (GMimeObjectStack, iter->parent, parent);
145 	g_slice_free (GMimePartIter, iter);
146 }
147 
148 
149 /**
150  * g_mime_part_iter_reset:
151  * @iter: a #GMimePartIter
152  *
153  * Resets the state of @iter to its initial state.
154  **/
155 void
g_mime_part_iter_reset(GMimePartIter * iter)156 g_mime_part_iter_reset (GMimePartIter *iter)
157 {
158 	g_return_if_fail (iter != NULL);
159 
160 	if (GMIME_IS_MESSAGE (iter->toplevel))
161 		iter->current = g_mime_message_get_mime_part ((GMimeMessage *) iter->toplevel);
162 	else
163 		iter->current = iter->toplevel;
164 
165 	g_slice_free_chain (GMimeObjectStack, iter->parent, parent);
166 	g_array_set_size (iter->path, 0);
167 	iter->parent = NULL;
168 	iter->index = -1;
169 
170 	if (!GMIME_IS_PART (iter->current)) {
171 		/* set our initial 'current' part to our first child */
172 		g_mime_part_iter_next (iter);
173 	}
174 }
175 
176 
177 /**
178  * g_mime_part_iter_jump_to:
179  * @iter: a #GMimePartIter
180  * @path: a string representing the path to jump to
181  *
182  * Updates the state of @iter to point to the #GMimeObject specified
183  * by @path.
184  *
185  * Returns: %TRUE if the #GMimeObject specified by @path exists or
186  * %FALSE otherwise.
187  **/
188 gboolean
g_mime_part_iter_jump_to(GMimePartIter * iter,const char * path)189 g_mime_part_iter_jump_to (GMimePartIter *iter, const char *path)
190 {
191 	GMimeMessagePart *message_part;
192 	GMimeMultipart *multipart;
193 	GMimeMessage *message;
194 	GMimeObject *current;
195 	GMimeObject *parent;
196 	const char *inptr;
197 	int index;
198 	char *dot;
199 
200 	g_return_val_if_fail (iter != NULL, FALSE);
201 
202 	if (!path || !path[0])
203 		return FALSE;
204 
205 	g_mime_part_iter_reset (iter);
206 
207 	if (!strcmp (path, "0"))
208 		return TRUE;
209 
210 	parent = iter->parent->object;
211 	iter->current = NULL;
212 	current = NULL;
213 	inptr = path;
214 	index = -1;
215 
216 	while (*inptr) {
217 		/* Note: path components are 1-based instead of 0-based */
218 		if ((index = strtol (inptr, &dot, 10)) <= 0 || errno == ERANGE ||
219 		    index == G_MAXINT || !(*dot == '.' || *dot == '\0'))
220 			return FALSE;
221 
222 		/* normalize to a 0-based index */
223 		index--;
224 
225 		if (GMIME_IS_MESSAGE_PART (parent)) {
226 			message_part = (GMimeMessagePart *) parent;
227 			if (!(message = g_mime_message_part_get_message (message_part)))
228 				return FALSE;
229 
230 			if (!(parent = g_mime_message_get_mime_part (message)))
231 				return FALSE;
232 
233 			if (!GMIME_IS_MULTIPART (parent))
234 				return FALSE;
235 
236 			goto multipart;
237 		} else if (GMIME_IS_MULTIPART (parent)) {
238 		multipart:
239 			multipart = (GMimeMultipart *) parent;
240 			if (index >= g_mime_multipart_get_count (multipart))
241 				return FALSE;
242 
243 			current = g_mime_multipart_get_part (multipart, index);
244 			iter->index = index;
245 		} else if (GMIME_IS_MESSAGE (parent)) {
246 			message = (GMimeMessage *) parent;
247 			if (!(current = g_mime_message_get_mime_part (message)))
248 				return FALSE;
249 
250 			iter->index = -1;
251 		} else {
252 			return FALSE;
253 		}
254 
255 		if (*dot == '\0')
256 			break;
257 
258 		g_mime_part_iter_push (iter, current, iter->index);
259 		parent = current;
260 		current = NULL;
261 		index = -1;
262 
263 		if (*dot != '.')
264 			break;
265 
266 		inptr = dot + 1;
267 	}
268 
269 	iter->current = current;
270 	iter->index = index;
271 
272 	return current != NULL;
273 }
274 
275 
276 /**
277  * g_mime_part_iter_is_valid:
278  * @iter: a #GMimePartIter
279  *
280  * Checks that the current state of @iter is valid.
281  *
282  * Returns: %TRUE if @iter is valid or %FALSE otherwise.
283  **/
284 gboolean
g_mime_part_iter_is_valid(GMimePartIter * iter)285 g_mime_part_iter_is_valid (GMimePartIter *iter)
286 {
287 	g_return_val_if_fail (iter != NULL, FALSE);
288 
289 	return iter->current != NULL;
290 }
291 
292 
293 /**
294  * g_mime_part_iter_next:
295  * @iter: a #GMimePartIter
296  *
297  * Advances to the next part in the MIME structure used to initialize
298  * @iter.
299  *
300  * Returns: %TRUE if successful or %FALSE otherwise.
301  **/
302 gboolean
g_mime_part_iter_next(GMimePartIter * iter)303 g_mime_part_iter_next (GMimePartIter *iter)
304 {
305 	GMimeMessagePart *message_part;
306 	GMimeMultipart *multipart;
307 	GMimeObject *mime_part;
308 	GMimeMessage *message;
309 
310 	if (!g_mime_part_iter_is_valid (iter))
311 		return FALSE;
312 
313 	if (GMIME_IS_MESSAGE_PART (iter->current)) {
314 		/* descend into our children */
315 		message_part = (GMimeMessagePart *) iter->current;
316 		message = g_mime_message_part_get_message (message_part);
317 		mime_part = message ? g_mime_message_get_mime_part (message) : NULL;
318 		if (mime_part != NULL) {
319 			g_mime_part_iter_push (iter, iter->current, iter->index);
320 			iter->current = mime_part;
321 
322 			if (GMIME_IS_MULTIPART (mime_part)) {
323 				iter->index = -1;
324 				goto multipart;
325 			}
326 
327 			iter->index = 0;
328 
329 			return TRUE;
330 		}
331 	} else if (GMIME_IS_MULTIPART (iter->current)) {
332 		/* descend into our children */
333 	multipart:
334 		multipart = (GMimeMultipart *) iter->current;
335 		if (g_mime_multipart_get_count (multipart) > 0) {
336 			g_mime_part_iter_push (iter, iter->current, iter->index);
337 			iter->current = g_mime_multipart_get_part (multipart, 0);
338 			iter->index = 0;
339 			return TRUE;
340 		}
341 	}
342 
343 	/* find the next sibling */
344 	while (iter->parent) {
345 		if (GMIME_IS_MULTIPART (iter->parent->object)) {
346 			/* iterate to the next part in the multipart */
347 			multipart = (GMimeMultipart *) iter->parent->object;
348 			iter->index++;
349 
350 			if (g_mime_multipart_get_count (multipart) > iter->index) {
351 				iter->current = g_mime_multipart_get_part (multipart, iter->index);
352 				return TRUE;
353 			}
354 		}
355 
356 		if (!g_mime_part_iter_pop (iter))
357 			break;
358 	}
359 
360 	iter->current = NULL;
361 	iter->index = -1;
362 
363 	return FALSE;
364 }
365 
366 
367 /**
368  * g_mime_part_iter_prev:
369  * @iter: a #GMimePartIter
370  *
371  * Rewinds to the previous part in the MIME structure used to
372  * initialize @iter.
373  *
374  * Returns: %TRUE if successful or %FALSE otherwise.
375  **/
376 gboolean
g_mime_part_iter_prev(GMimePartIter * iter)377 g_mime_part_iter_prev (GMimePartIter *iter)
378 {
379 	GMimeMultipart *multipart;
380 
381 	if (!g_mime_part_iter_is_valid (iter))
382 		return FALSE;
383 
384 	if (iter->parent == NULL) {
385 		iter->current = NULL;
386 		iter->index = -1;
387 		return FALSE;
388 	}
389 
390 	if (GMIME_IS_MULTIPART (iter->parent->object)) {
391 		/* revert backward to the previous part in the multipart */
392 		multipart = (GMimeMultipart *) iter->parent->object;
393 		iter->index--;
394 
395 		if (iter->index >= 0) {
396 			iter->current = g_mime_multipart_get_part (multipart, iter->index);
397 			return TRUE;
398 		}
399 	}
400 
401 	return g_mime_part_iter_pop (iter);
402 }
403 
404 
405 /**
406  * g_mime_part_iter_get_toplevel:
407  * @iter: a #GMimePartIter
408  *
409  * Gets the toplevel #GMimeObject used to initialize @iter.
410  *
411  * Returns: (transfer none): the toplevel #GMimeObject.
412  **/
413 GMimeObject *
g_mime_part_iter_get_toplevel(GMimePartIter * iter)414 g_mime_part_iter_get_toplevel (GMimePartIter *iter)
415 {
416 	g_return_val_if_fail (iter != NULL, NULL);
417 
418 	return iter->toplevel;
419 }
420 
421 
422 /**
423  * g_mime_part_iter_get_current:
424  * @iter: a #GMimePartIter
425  *
426  * Gets the #GMimeObject at the current #GMimePartIter position.
427  *
428  * Returns: (transfer none): the current #GMimeObject or %NULL if the
429  * state of @iter is invalid.
430  **/
431 GMimeObject *
g_mime_part_iter_get_current(GMimePartIter * iter)432 g_mime_part_iter_get_current (GMimePartIter *iter)
433 {
434 	g_return_val_if_fail (iter != NULL, NULL);
435 
436 	return iter->current;
437 }
438 
439 
440 /**
441  * g_mime_part_iter_get_parent:
442  * @iter: a #GMimePartIter
443  *
444  * Gets the parent of the #GMimeObject at the current #GMimePartIter
445  * position.
446  *
447  * Returns: (transfer none): the parent #GMimeObject or %NULL if the
448  * state of @iter is invalid.
449  **/
450 GMimeObject *
g_mime_part_iter_get_parent(GMimePartIter * iter)451 g_mime_part_iter_get_parent (GMimePartIter *iter)
452 {
453 	g_return_val_if_fail (iter != NULL, NULL);
454 
455 	if (!g_mime_part_iter_is_valid (iter))
456 		return NULL;
457 
458 	return iter->parent ? iter->parent->object : NULL;
459 }
460 
461 
462 /**
463  * g_mime_part_iter_get_path:
464  * @iter: a #GMimePartIter
465  *
466  * Gets the path of the current #GMimeObject in the MIME structure
467  * used to initialize @iter.
468  *
469  * Returns: a newly allocated string representation of the path to the
470  * #GMimeObject at the current #GMimePartIter position.
471  **/
472 char *
g_mime_part_iter_get_path(GMimePartIter * iter)473 g_mime_part_iter_get_path (GMimePartIter *iter)
474 {
475 	GString *path;
476 	guint i;
477 	int v;
478 
479 	if (!g_mime_part_iter_is_valid (iter))
480 		return NULL;
481 
482 	/* Note: path components are 1-based instead of 0-based */
483 
484 	path = g_string_new ("");
485 	for (i = 0; i < iter->path->len; i++) {
486 		v = g_array_index (iter->path, int, i);
487 		g_string_append_printf (path, "%d.", v + 1);
488 	}
489 
490 	g_string_append_printf (path, "%d", iter->index + 1);
491 
492 	return g_string_free (path, FALSE);
493 }
494 
495 
496 /**
497  * g_mime_part_iter_replace:
498  * @iter: a #GMimePartIter
499  * @replacement: a #GMimeObject
500  *
501  * Replaces the #GMimeObject at the current position with @replacement.
502  *
503  * Returns: %TRUE if the part at the current position was replaced or
504  * %FALSE otherwise.
505  **/
506 gboolean
g_mime_part_iter_replace(GMimePartIter * iter,GMimeObject * replacement)507 g_mime_part_iter_replace (GMimePartIter *iter, GMimeObject *replacement)
508 {
509 	GMimeMessagePart *message_part;
510 	GMimeMessage *message;
511 	GMimeObject *current;
512 	GMimeObject *parent;
513 	int index;
514 
515 	g_return_val_if_fail (GMIME_IS_OBJECT (replacement), FALSE);
516 
517 	if (!g_mime_part_iter_is_valid (iter))
518 		return FALSE;
519 
520 	if (iter->current == iter->toplevel) {
521 		g_object_unref (iter->toplevel);
522 		iter->toplevel = replacement;
523 		g_object_ref (replacement);
524 		return TRUE;
525 	}
526 
527 	parent = iter->parent ? iter->parent->object : iter->toplevel;
528 	index = iter->index;
529 
530 	/* now we can safely replace the previously referenced part in its parent */
531 	if (GMIME_IS_MESSAGE_PART (parent)) {
532 		/* depending on what we've been given as a
533 		 * replacement, we might replace the message in the
534 		 * message/rfc822 part or we might end up replacing
535 		 * the toplevel mime part of said message. */
536 		message_part = (GMimeMessagePart *) parent;
537 		message = g_mime_message_part_get_message (message_part);
538 		if (GMIME_IS_MESSAGE (replacement))
539 			g_mime_message_part_set_message (message_part, (GMimeMessage *) replacement);
540 		else
541 			g_mime_message_set_mime_part (message, replacement);
542 	} else if (GMIME_IS_MULTIPART (parent)) {
543 		current = g_mime_multipart_replace ((GMimeMultipart *) parent, index, replacement);
544 		g_object_unref (current);
545 	} else if (GMIME_IS_MESSAGE (parent)) {
546 		g_mime_message_set_mime_part ((GMimeMessage *) parent, replacement);
547 	} else {
548 		g_assert_not_reached ();
549 	}
550 
551 	iter->current = replacement;
552 
553 	return TRUE;
554 }
555 
556 
557 /**
558  * g_mime_part_iter_remove:
559  * @iter: a #GMimePartIter
560  *
561  * Removes the #GMimeObject at the current position from its
562  * parent. If successful, @iter is advanced to the next position
563  * (since the current position will become invalid).
564  *
565  * Returns: %TRUE if the part at the current position was removed or
566  * %FALSE otherwise.
567  **/
568 gboolean
g_mime_part_iter_remove(GMimePartIter * iter)569 g_mime_part_iter_remove (GMimePartIter *iter)
570 {
571 	GMimeObject *current;
572 	GMimeObject *parent;
573 	int index;
574 
575 	if (!g_mime_part_iter_is_valid (iter))
576 		return FALSE;
577 
578 	if (iter->current == iter->toplevel)
579 		return FALSE;
580 
581 	parent = iter->parent ? iter->parent->object : iter->toplevel;
582 	current = iter->current;
583 	index = iter->index;
584 
585 	/* iterate to the next part so we have something valid to refer to */
586 	g_mime_part_iter_next (iter);
587 
588 	/* now we can safely remove the previously referenced part from its parent */
589 	if (GMIME_IS_MESSAGE_PART (parent)) {
590 		g_mime_message_part_set_message ((GMimeMessagePart *) parent, NULL);
591 	} else if (GMIME_IS_MULTIPART (parent)) {
592 		g_mime_multipart_remove_at ((GMimeMultipart *) parent, index);
593 		g_object_unref (current);
594 	} else if (GMIME_IS_MESSAGE (parent)) {
595 		g_mime_message_set_mime_part ((GMimeMessage *) parent, NULL);
596 	} else {
597 		g_assert_not_reached ();
598 	}
599 
600 	return TRUE;
601 }
602