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