fix #3869: outdated FSF address
[oweals/gnunet.git] / src / util / container_meta_data.c
1 /*
2      This file is part of GNUnet.
3      Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009, 2010 Christian Grothoff (and other contributing authors)
4
5      GNUnet is free software; you can redistribute it and/or modify
6      it under the terms of the GNU General Public License as published
7      by the Free Software Foundation; either version 3, or (at your
8      option) any later version.
9
10      GNUnet is distributed in the hope that it will be useful, but
11      WITHOUT ANY WARRANTY; without even the implied warranty of
12      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13      General Public License for more details.
14
15      You should have received a copy of the GNU General Public License
16      along with GNUnet; see the file COPYING.  If not, write to the
17      Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
18      Boston, MA 02110-1301, USA.
19 */
20
21 /**
22  * @file util/container_meta_data.c
23  * @brief Storing of meta data
24  * @author Christian Grothoff
25  */
26
27 #include "platform.h"
28 #include "gnunet_util_lib.h"
29 #if HAVE_EXTRACTOR_H
30 #include <extractor.h>
31 #endif
32 #include <zlib.h>
33
34 #define LOG(kind,...) GNUNET_log_from (kind, "util", __VA_ARGS__)
35
36 /**
37  * Meta data item.
38  */
39 struct MetaItem
40 {
41   /**
42    * This is a doubly linked list.
43    */
44   struct MetaItem *next;
45
46   /**
47    * This is a doubly linked list.
48    */
49   struct MetaItem *prev;
50
51   /**
52    * Name of the extracting plugin.
53    */
54   char *plugin_name;
55
56   /**
57    * Mime-type of data.
58    */
59   char *mime_type;
60
61   /**
62    * The actual meta data.
63    */
64   char *data;
65
66   /**
67    * Number of bytes in 'data'.
68    */
69   size_t data_size;
70
71   /**
72    * Type of the meta data.
73    */
74   enum EXTRACTOR_MetaType type;
75
76   /**
77    * Format of the meta data.
78    */
79   enum EXTRACTOR_MetaFormat format;
80
81 };
82
83 /**
84  * Meta data to associate with a file, directory or namespace.
85  */
86 struct GNUNET_CONTAINER_MetaData
87 {
88   /**
89    * Head of linked list of the meta data items.
90    */
91   struct MetaItem *items_head;
92
93   /**
94    * Tail of linked list of the meta data items.
95    */
96   struct MetaItem *items_tail;
97
98   /**
99    * Complete serialized and compressed buffer of the items.
100    * NULL if we have not computed that buffer yet.
101    */
102   char *sbuf;
103
104   /**
105    * Number of bytes in 'sbuf'. 0 if the buffer is stale.
106    */
107   size_t sbuf_size;
108
109   /**
110    * Number of items in the linked list.
111    */
112   unsigned int item_count;
113
114 };
115
116
117 /**
118  * Create a fresh struct CONTAINER_MetaData token.
119  *
120  * @return empty meta-data container
121  */
122 struct GNUNET_CONTAINER_MetaData *
123 GNUNET_CONTAINER_meta_data_create ()
124 {
125   return GNUNET_new (struct GNUNET_CONTAINER_MetaData);
126 }
127
128
129 /**
130  * Free meta data item.
131  *
132  * @param mi item to free
133  */
134 static void
135 meta_item_free (struct MetaItem *mi)
136 {
137   GNUNET_free_non_null (mi->plugin_name);
138   GNUNET_free_non_null (mi->mime_type);
139   GNUNET_free_non_null (mi->data);
140   GNUNET_free (mi);
141 }
142
143
144 /**
145  * The meta data has changed, invalidate its serialization
146  * buffer.
147  *
148  * @param md meta data that changed
149  */
150 static void
151 invalidate_sbuf (struct GNUNET_CONTAINER_MetaData *md)
152 {
153   if (NULL == md->sbuf)
154     return;
155   GNUNET_free (md->sbuf);
156   md->sbuf = NULL;
157   md->sbuf_size = 0;
158 }
159
160
161 /**
162  * Free meta data.
163  *
164  * @param md what to free
165  */
166 void
167 GNUNET_CONTAINER_meta_data_destroy (struct GNUNET_CONTAINER_MetaData *md)
168 {
169   struct MetaItem *pos;
170
171   if (NULL == md)
172     return;
173   while (NULL != (pos = md->items_head))
174   {
175     GNUNET_CONTAINER_DLL_remove (md->items_head, md->items_tail, pos);
176     meta_item_free (pos);
177   }
178   GNUNET_free_non_null (md->sbuf);
179   GNUNET_free (md);
180 }
181
182
183 /**
184  * Remove all items in the container.
185  *
186  * @param md metadata to manipulate
187  */
188 void
189 GNUNET_CONTAINER_meta_data_clear (struct GNUNET_CONTAINER_MetaData *md)
190 {
191   struct MetaItem *mi;
192
193   if (NULL == md)
194     return;
195   while (NULL != (mi = md->items_head))
196   {
197     GNUNET_CONTAINER_DLL_remove (md->items_head, md->items_tail, mi);
198     meta_item_free (mi);
199   }
200   GNUNET_free_non_null (md->sbuf);
201   memset (md, 0, sizeof (struct GNUNET_CONTAINER_MetaData));
202 }
203
204
205 /**
206  * Test if two MDs are equal.  We consider them equal if
207  * the meta types, formats and content match (we do not
208  * include the mime types and plugins names in this
209  * consideration).
210  *
211  * @param md1 first value to check
212  * @param md2 other value to check
213  * @return #GNUNET_YES if they are equal
214  */
215 int
216 GNUNET_CONTAINER_meta_data_test_equal (const struct GNUNET_CONTAINER_MetaData
217                                        *md1,
218                                        const struct GNUNET_CONTAINER_MetaData
219                                        *md2)
220 {
221   struct MetaItem *i;
222   struct MetaItem *j;
223   int found;
224
225   if (md1 == md2)
226     return GNUNET_YES;
227   if (md1->item_count != md2->item_count)
228     return GNUNET_NO;
229   for (i = md1->items_head; NULL != i; i = i->next)
230   {
231     found = GNUNET_NO;
232     for (j = md2->items_head; NULL != j; j = j->next)
233     {
234       if ((i->type == j->type) && (i->format == j->format) &&
235           (i->data_size == j->data_size) &&
236           (0 == memcmp (i->data, j->data, i->data_size)))
237       {
238         found = GNUNET_YES;
239         break;
240       }
241       if (j->data_size < i->data_size)
242         break; /* elements are sorted by (decreasing) size... */
243     }
244     if (GNUNET_NO == found)
245       return GNUNET_NO;
246   }
247   return GNUNET_YES;
248 }
249
250
251 /**
252  * Extend metadata.  Note that the list of meta data items is
253  * sorted by size (largest first).
254  *
255  * @param md metadata to extend
256  * @param plugin_name name of the plugin that produced this value;
257  *        special values can be used (i.e. '&lt;zlib&gt;' for zlib being
258  *        used in the main libextractor library and yielding
259  *        meta data).
260  * @param type libextractor-type describing the meta data
261  * @param format basic format information about data
262  * @param data_mime_type mime-type of data (not of the original file);
263  *        can be NULL (if mime-type is not known)
264  * @param data actual meta-data found
265  * @param data_size number of bytes in @a data
266  * @return #GNUNET_OK on success, #GNUNET_SYSERR if this entry already exists
267  *         data_mime_type and plugin_name are not considered for "exists" checks
268  */
269 int
270 GNUNET_CONTAINER_meta_data_insert (struct GNUNET_CONTAINER_MetaData *md,
271                                    const char *plugin_name,
272                                    enum EXTRACTOR_MetaType type,
273                                    enum EXTRACTOR_MetaFormat format,
274                                    const char *data_mime_type, const char *data,
275                                    size_t data_size)
276 {
277   struct MetaItem *pos;
278   struct MetaItem *mi;
279   char *p;
280
281   if ((EXTRACTOR_METAFORMAT_UTF8 == format) ||
282       (EXTRACTOR_METAFORMAT_C_STRING == format))
283     GNUNET_break ('\0' == data[data_size - 1]);
284
285   for (pos = md->items_head; NULL != pos; pos = pos->next)
286   {
287     if (pos->data_size < data_size)
288       break; /* elements are sorted by size in the list */
289     if ((pos->type == type) && (pos->data_size == data_size) &&
290         (0 == memcmp (pos->data, data, data_size)))
291     {
292       if ((NULL == pos->mime_type) && (NULL != data_mime_type))
293       {
294         pos->mime_type = GNUNET_strdup (data_mime_type);
295         invalidate_sbuf (md);
296       }
297       if ((EXTRACTOR_METAFORMAT_C_STRING == pos->format) &&
298           (EXTRACTOR_METAFORMAT_UTF8 == format))
299       {
300         pos->format = EXTRACTOR_METAFORMAT_UTF8;
301         invalidate_sbuf (md);
302       }
303       return GNUNET_SYSERR;
304     }
305   }
306   md->item_count++;
307   mi = GNUNET_new (struct MetaItem);
308   mi->type = type;
309   mi->format = format;
310   mi->data_size = data_size;
311   if (NULL == pos)
312     GNUNET_CONTAINER_DLL_insert_tail (md->items_head,
313                                       md->items_tail,
314                                       mi);
315   else
316     GNUNET_CONTAINER_DLL_insert_after (md->items_head,
317                                        md->items_tail,
318                                        pos->prev,
319                                        mi);
320   mi->mime_type =
321       (NULL == data_mime_type) ? NULL : GNUNET_strdup (data_mime_type);
322   mi->plugin_name = (NULL == plugin_name) ? NULL : GNUNET_strdup (plugin_name);
323   mi->data = GNUNET_malloc (data_size);
324   memcpy (mi->data, data, data_size);
325   /* change all dir separators to POSIX style ('/') */
326   if ( (EXTRACTOR_METATYPE_FILENAME == type) ||
327        (EXTRACTOR_METATYPE_GNUNET_ORIGINAL_FILENAME == type) )
328   {
329     p = mi->data;
330     while (('\0' != *p) && (p < mi->data + data_size))
331     {
332       if ('\\' == *p)
333         *p = '/';
334       p++;
335     }
336   }
337   invalidate_sbuf (md);
338   return GNUNET_OK;
339 }
340
341
342 /**
343  * Merge given meta data.
344  *
345  * @param cls the `struct GNUNET_CONTAINER_MetaData` to merge into
346  * @param plugin_name name of the plugin that produced this value;
347  *        special values can be used (i.e. '&lt;zlib&gt;' for zlib being
348  *        used in the main libextractor library and yielding
349  *        meta data).
350  * @param type libextractor-type describing the meta data
351  * @param format basic format information about data
352  * @param data_mime_type mime-type of data (not of the original file);
353  *        can be NULL (if mime-type is not known)
354  * @param data actual meta-data found
355  * @param data_size number of bytes in @a data
356  * @return 0 (to continue)
357  */
358 static int
359 merge_helper (void *cls, const char *plugin_name, enum EXTRACTOR_MetaType type,
360               enum EXTRACTOR_MetaFormat format, const char *data_mime_type,
361               const char *data, size_t data_size)
362 {
363   struct GNUNET_CONTAINER_MetaData *md = cls;
364
365   (void) GNUNET_CONTAINER_meta_data_insert (md, plugin_name, type, format,
366                                             data_mime_type, data, data_size);
367   return 0;
368 }
369
370
371 /**
372  * Extend metadata.  Merges the meta data from the second argument
373  * into the first, discarding duplicate key-value pairs.
374  *
375  * @param md metadata to extend
376  * @param in metadata to merge
377  */
378 void
379 GNUNET_CONTAINER_meta_data_merge (struct GNUNET_CONTAINER_MetaData *md,
380                                   const struct GNUNET_CONTAINER_MetaData *in)
381 {
382   GNUNET_CONTAINER_meta_data_iterate (in, &merge_helper, md);
383 }
384
385
386 /**
387  * Remove an item.
388  *
389  * @param md metadata to manipulate
390  * @param type type of the item to remove
391  * @param data specific value to remove, NULL to remove all
392  *        entries of the given type
393  * @param data_size number of bytes in @a data
394  * @return #GNUNET_OK on success, #GNUNET_SYSERR if the item does not exist in md
395  */
396 int
397 GNUNET_CONTAINER_meta_data_delete (struct GNUNET_CONTAINER_MetaData *md,
398                                    enum EXTRACTOR_MetaType type,
399                                    const char *data, size_t data_size)
400 {
401   struct MetaItem *pos;
402
403   for (pos = md->items_head; NULL != pos; pos = pos->next)
404   {
405     if (pos->data_size < data_size)
406       break; /* items are sorted by (decreasing) size */
407     if ((pos->type == type) &&
408         ((NULL == data) ||
409          ((pos->data_size == data_size) &&
410           (0 == memcmp (pos->data, data, data_size)))))
411     {
412       GNUNET_CONTAINER_DLL_remove (md->items_head, md->items_tail, pos);
413       meta_item_free (pos);
414       md->item_count--;
415       invalidate_sbuf (md);
416       return GNUNET_OK;
417     }
418   }
419   return GNUNET_SYSERR;
420 }
421
422
423 /**
424  * Add the current time as the publication date
425  * to the meta-data.
426  *
427  * @param md metadata to modify
428  */
429 void
430 GNUNET_CONTAINER_meta_data_add_publication_date (struct
431                                                  GNUNET_CONTAINER_MetaData *md)
432 {
433   const char *dat;
434   struct GNUNET_TIME_Absolute t;
435
436   t = GNUNET_TIME_absolute_get ();
437   GNUNET_CONTAINER_meta_data_delete (md,
438                                      EXTRACTOR_METATYPE_PUBLICATION_DATE,
439                                      NULL, 0);
440   dat = GNUNET_STRINGS_absolute_time_to_string (t);
441   GNUNET_CONTAINER_meta_data_insert (md, "<gnunet>",
442                                      EXTRACTOR_METATYPE_PUBLICATION_DATE,
443                                      EXTRACTOR_METAFORMAT_UTF8, "text/plain",
444                                      dat, strlen (dat) + 1);
445 }
446
447
448 /**
449  * Iterate over MD entries.
450  *
451  * @param md metadata to inspect
452  * @param iter function to call on each entry
453  * @param iter_cls closure for iterator
454  * @return number of entries
455  */
456 int
457 GNUNET_CONTAINER_meta_data_iterate (const struct GNUNET_CONTAINER_MetaData *md,
458                                     EXTRACTOR_MetaDataProcessor iter,
459                                     void *iter_cls)
460 {
461   struct MetaItem *pos;
462
463   if (NULL == md)
464     return 0;
465   if (NULL == iter)
466     return md->item_count;
467   for (pos = md->items_head; NULL != pos; pos = pos->next)
468     if (0 !=
469         iter (iter_cls, pos->plugin_name, pos->type, pos->format,
470               pos->mime_type, pos->data, pos->data_size))
471       return md->item_count;
472   return md->item_count;
473 }
474
475
476 /**
477  * Get the first MD entry of the given type.  Caller
478  * is responsible for freeing the return value.
479  * Also, only meta data items that are strings (0-terminated)
480  * are returned by this function.
481  *
482  * @param md metadata to inspect
483  * @param type type to look for
484  * @return NULL if no entry was found
485  */
486 char *
487 GNUNET_CONTAINER_meta_data_get_by_type (const struct GNUNET_CONTAINER_MetaData *md,
488                                         enum EXTRACTOR_MetaType type)
489 {
490   struct MetaItem *pos;
491
492   if (NULL == md)
493     return NULL;
494   for (pos = md->items_head; NULL != pos; pos = pos->next)
495     if ((type == pos->type) &&
496         ((pos->format == EXTRACTOR_METAFORMAT_UTF8) ||
497          (pos->format == EXTRACTOR_METAFORMAT_C_STRING)))
498       return GNUNET_strdup (pos->data);
499   return NULL;
500 }
501
502
503 /**
504  * Get the first matching MD entry of the given types. Caller is
505  * responsible for freeing the return value.  Also, only meta data
506  * items that are strings (0-terminated) are returned by this
507  * function.
508  *
509  * @param md metadata to inspect
510  * @param ... -1-terminated list of types
511  * @return NULL if we do not have any such entry,
512  *  otherwise client is responsible for freeing the value!
513  */
514 char *
515 GNUNET_CONTAINER_meta_data_get_first_by_types (const struct
516                                                GNUNET_CONTAINER_MetaData *md,
517                                                ...)
518 {
519   char *ret;
520   va_list args;
521   enum EXTRACTOR_MetaType type;
522
523   if (NULL == md)
524     return NULL;
525   ret = NULL;
526   va_start (args, md);
527   while (1)
528   {
529     type = va_arg (args, enum EXTRACTOR_MetaType);
530     if (-1 == type)
531       break;
532     if (NULL != (ret = GNUNET_CONTAINER_meta_data_get_by_type (md, type)))
533       break;
534   }
535   va_end (args);
536   return ret;
537 }
538
539
540 /**
541  * Get a thumbnail from the meta-data (if present).
542  *
543  * @param md metadata to get the thumbnail from
544  * @param thumb will be set to the thumbnail data.  Must be
545  *        freed by the caller!
546  * @return number of bytes in thumbnail, 0 if not available
547  */
548 size_t
549 GNUNET_CONTAINER_meta_data_get_thumbnail (const struct GNUNET_CONTAINER_MetaData
550                                           * md, unsigned char **thumb)
551 {
552   struct MetaItem *pos;
553   struct MetaItem *match;
554
555   if (NULL == md)
556     return 0;
557   match = NULL;
558   for (pos = md->items_head; NULL != pos; pos = pos->next)
559   {
560     if ((NULL != pos->mime_type) &&
561         (0 == strncasecmp ("image/", pos->mime_type, strlen ("image/"))) &&
562         (EXTRACTOR_METAFORMAT_BINARY == pos->format))
563     {
564       if (NULL == match)
565         match = pos;
566       else if ((match->type != EXTRACTOR_METATYPE_THUMBNAIL) &&
567                (pos->type == EXTRACTOR_METATYPE_THUMBNAIL))
568         match = pos;
569     }
570   }
571   if ((NULL == match) || (0 == match->data_size))
572     return 0;
573   *thumb = GNUNET_malloc (match->data_size);
574   memcpy (*thumb, match->data, match->data_size);
575   return match->data_size;
576 }
577
578
579 /**
580  * Duplicate a `struct GNUNET_CONTAINER_MetaData`.
581  *
582  * @param md what to duplicate
583  * @return duplicate meta-data container
584  */
585 struct GNUNET_CONTAINER_MetaData *
586 GNUNET_CONTAINER_meta_data_duplicate (const struct GNUNET_CONTAINER_MetaData
587                                       *md)
588 {
589   struct GNUNET_CONTAINER_MetaData *ret;
590   struct MetaItem *pos;
591
592   if (NULL == md)
593     return NULL;
594   ret = GNUNET_CONTAINER_meta_data_create ();
595   for (pos = md->items_tail; NULL != pos; pos = pos->prev)
596     GNUNET_CONTAINER_meta_data_insert (ret, pos->plugin_name, pos->type,
597                                        pos->format, pos->mime_type, pos->data,
598                                        pos->data_size);
599   return ret;
600 }
601
602
603
604 /**
605  * Try to compress the given block of data.
606  *
607  * @param data block to compress; if compression
608  *        resulted in a smaller block, the first
609  *        bytes of data are updated to the compressed
610  *        data
611  * @param oldSize number of bytes in data
612  * @param result set to the compressed data
613  * @param newSize set to size of result
614  * @return #GNUNET_YES if compression reduce the size,
615  *         #GNUNET_NO if compression did not help
616  */
617 static int
618 try_compression (const char *data, size_t oldSize, char **result,
619                  size_t * newSize)
620 {
621   char *tmp;
622   uLongf dlen;
623
624 #ifdef compressBound
625   dlen = compressBound (oldSize);
626 #else
627   dlen = oldSize + (oldSize / 100) + 20;
628   /* documentation says 100.1% oldSize + 12 bytes, but we
629    * should be able to overshoot by more to be safe */
630 #endif
631   tmp = GNUNET_malloc (dlen);
632   if (Z_OK ==
633       compress2 ((Bytef *) tmp, &dlen, (const Bytef *) data, oldSize, 9))
634   {
635     if (dlen < oldSize)
636     {
637       *result = tmp;
638       *newSize = dlen;
639       return GNUNET_YES;
640     }
641   }
642   GNUNET_free (tmp);
643   return GNUNET_NO;
644 }
645
646
647 /**
648  * Flag in 'version' that indicates compressed meta-data.
649  */
650 #define HEADER_COMPRESSED 0x80000000
651
652
653 /**
654  * Bits in 'version' that give the version number.
655  */
656 #define HEADER_VERSION_MASK 0x7FFFFFFF
657
658
659 /**
660  * Header for serialized meta data.
661  */
662 struct MetaDataHeader
663 {
664   /**
665    * The version of the MD serialization.  The highest bit is used to
666    * indicate compression.
667    *
668    * Version 0 is traditional (pre-0.9) meta data (unsupported)
669    * Version is 1 for a NULL pointer
670    * Version 2 is for 0.9.x (and possibly higher)
671    * Other version numbers are not yet defined.
672    */
673   uint32_t version;
674
675   /**
676    * How many MD entries are there?
677    */
678   uint32_t entries;
679
680   /**
681    * Size of the decompressed meta data.
682    */
683   uint32_t size;
684
685   /**
686    * This is followed by 'entries' values of type 'struct MetaDataEntry'
687    * and then by 'entry' plugin names, mime-types and data blocks
688    * as specified in those meta data entries.
689    */
690 };
691
692
693 /**
694  * Entry of serialized meta data.
695  */
696 struct MetaDataEntry
697 {
698   /**
699    * Meta data type.  Corresponds to an 'enum EXTRACTOR_MetaType'
700    */
701   uint32_t type;
702
703   /**
704    * Meta data format. Corresponds to an 'enum EXTRACTOR_MetaFormat'
705    */
706   uint32_t format;
707
708   /**
709    * Number of bytes of meta data.
710    */
711   uint32_t data_size;
712
713   /**
714    * Number of bytes in the plugin name including 0-terminator.  0 for NULL.
715    */
716   uint32_t plugin_name_len;
717
718   /**
719    * Number of bytes in the mime type including 0-terminator.  0 for NULL.
720    */
721   uint32_t mime_type_len;
722
723 };
724
725
726 /**
727  * Serialize meta-data to target.
728  *
729  * @param md metadata to serialize
730  * @param target where to write the serialized metadata;
731  *         *target can be NULL, in which case memory is allocated
732  * @param max maximum number of bytes available in target
733  * @param opt is it ok to just write SOME of the
734  *        meta-data to match the size constraint,
735  *        possibly discarding some data?
736  * @return number of bytes written on success,
737  *         #GNUNET_SYSERR on error (typically: not enough
738  *         space)
739  */
740 ssize_t
741 GNUNET_CONTAINER_meta_data_serialize (const struct GNUNET_CONTAINER_MetaData
742                                       *md, char **target, size_t max,
743                                       enum
744                                       GNUNET_CONTAINER_MetaDataSerializationOptions
745                                       opt)
746 {
747   struct GNUNET_CONTAINER_MetaData *vmd;
748   struct MetaItem *pos;
749   struct MetaDataHeader ihdr;
750   struct MetaDataHeader *hdr;
751   struct MetaDataEntry *ent;
752   char *dst;
753   unsigned int i;
754   uint64_t msize;
755   size_t off;
756   char *mdata;
757   char *cdata;
758   size_t mlen;
759   size_t plen;
760   size_t size;
761   size_t left;
762   size_t clen;
763   size_t rlen;
764   int comp;
765
766   if (max < sizeof (struct MetaDataHeader))
767     return GNUNET_SYSERR;       /* far too small */
768   if (NULL == md)
769     return 0;
770
771   if (NULL != md->sbuf)
772   {
773     /* try to use serialization cache */
774     if (md->sbuf_size <= max)
775     {
776       if (NULL == *target)
777         *target = GNUNET_malloc (md->sbuf_size);
778       memcpy (*target, md->sbuf, md->sbuf_size);
779       return md->sbuf_size;
780     }
781     if (0 == (opt & GNUNET_CONTAINER_META_DATA_SERIALIZE_PART))
782       return GNUNET_SYSERR;     /* can say that this will fail */
783     /* need to compute a partial serialization, sbuf useless ... */
784   }
785   dst = NULL;
786   msize = 0;
787   for (pos = md->items_tail; NULL != pos; pos = pos->prev)
788   {
789     msize += sizeof (struct MetaDataEntry);
790     msize += pos->data_size;
791     if (NULL != pos->plugin_name)
792       msize += strlen (pos->plugin_name) + 1;
793     if (NULL != pos->mime_type)
794       msize += strlen (pos->mime_type) + 1;
795   }
796   size = (size_t) msize;
797   if (size != msize)
798   {
799     GNUNET_break (0);           /* integer overflow */
800     return GNUNET_SYSERR;
801   }
802   if (size >= GNUNET_MAX_MALLOC_CHECKED)
803   {
804     /* too large to be processed */
805     return GNUNET_SYSERR;
806   }
807   ent = GNUNET_malloc (size);
808   mdata = (char *) &ent[md->item_count];
809   off = size - (md->item_count * sizeof (struct MetaDataEntry));
810   i = 0;
811   for (pos = md->items_head; NULL != pos; pos = pos->next)
812   {
813     ent[i].type = htonl ((uint32_t) pos->type);
814     ent[i].format = htonl ((uint32_t) pos->format);
815     ent[i].data_size = htonl ((uint32_t) pos->data_size);
816     if (NULL == pos->plugin_name)
817       plen = 0;
818     else
819       plen = strlen (pos->plugin_name) + 1;
820     ent[i].plugin_name_len = htonl ((uint32_t) plen);
821     if (NULL == pos->mime_type)
822       mlen = 0;
823     else
824       mlen = strlen (pos->mime_type) + 1;
825     ent[i].mime_type_len = htonl ((uint32_t) mlen);
826     off -= pos->data_size;
827     if ((EXTRACTOR_METAFORMAT_UTF8 == pos->format) ||
828         (EXTRACTOR_METAFORMAT_C_STRING == pos->format))
829       GNUNET_break ('\0' == pos->data[pos->data_size - 1]);
830     memcpy (&mdata[off], pos->data, pos->data_size);
831     off -= plen;
832     if (NULL != pos->plugin_name)
833       memcpy (&mdata[off], pos->plugin_name, plen);
834     off -= mlen;
835     if (NULL != pos->mime_type)
836       memcpy (&mdata[off], pos->mime_type, mlen);
837     i++;
838   }
839   GNUNET_assert (0 == off);
840
841   clen = 0;
842   cdata = NULL;
843   left = size;
844   i = 0;
845   for (pos = md->items_head; NULL != pos; pos = pos->next)
846   {
847     comp = GNUNET_NO;
848     if (0 == (opt & GNUNET_CONTAINER_META_DATA_SERIALIZE_NO_COMPRESS))
849       comp = try_compression ((const char *) &ent[i], left, &cdata, &clen);
850
851     if ((NULL == md->sbuf) && (0 == i))
852     {
853       /* fill 'sbuf'; this "modifies" md, but since this is only
854        * an internal cache we will cast away the 'const' instead
855        * of making the API look strange. */
856       vmd = (struct GNUNET_CONTAINER_MetaData *) md;
857       hdr = GNUNET_malloc (left + sizeof (struct MetaDataHeader));
858       hdr->size = htonl (left);
859       hdr->entries = htonl (md->item_count);
860       if (GNUNET_YES == comp)
861       {
862         GNUNET_assert (clen < left);
863         hdr->version = htonl (2 | HEADER_COMPRESSED);
864         memcpy (&hdr[1], cdata, clen);
865         vmd->sbuf_size = clen + sizeof (struct MetaDataHeader);
866       }
867       else
868       {
869         hdr->version = htonl (2);
870         memcpy (&hdr[1], &ent[0], left);
871         vmd->sbuf_size = left + sizeof (struct MetaDataHeader);
872       }
873       vmd->sbuf = (char *) hdr;
874     }
875
876     if (((left + sizeof (struct MetaDataHeader)) <= max) ||
877         ((GNUNET_YES == comp) && (clen <= max)))
878     {
879       /* success, this now fits! */
880       if (GNUNET_YES == comp)
881       {
882         if (NULL == dst)
883           dst = GNUNET_malloc (clen + sizeof (struct MetaDataHeader));
884         hdr = (struct MetaDataHeader *) dst;
885         hdr->version = htonl (2 | HEADER_COMPRESSED);
886         hdr->size = htonl (left);
887         hdr->entries = htonl (md->item_count - i);
888         memcpy (&dst[sizeof (struct MetaDataHeader)], cdata, clen);
889         GNUNET_free (cdata);
890         cdata = NULL;
891         GNUNET_free (ent);
892         rlen = clen + sizeof (struct MetaDataHeader);
893       }
894       else
895       {
896         if (NULL == dst)
897           dst = GNUNET_malloc (left + sizeof (struct MetaDataHeader));
898         hdr = (struct MetaDataHeader *) dst;
899         hdr->version = htonl (2);
900         hdr->entries = htonl (md->item_count - i);
901         hdr->size = htonl (left);
902         memcpy (&dst[sizeof (struct MetaDataHeader)], &ent[i], left);
903         GNUNET_free (ent);
904         rlen = left + sizeof (struct MetaDataHeader);
905       }
906       if (NULL != *target)
907       {
908         if (GNUNET_YES == comp)
909           memcpy (*target, dst, clen + sizeof (struct MetaDataHeader));
910         else
911           memcpy (*target, dst, left + sizeof (struct MetaDataHeader));
912         GNUNET_free (dst);
913       }
914       else
915       {
916         *target = dst;
917       }
918       return rlen;
919     }
920
921     if (0 == (opt & GNUNET_CONTAINER_META_DATA_SERIALIZE_PART))
922     {
923       /* does not fit! */
924       GNUNET_free (ent);
925       return GNUNET_SYSERR;
926     }
927
928     /* next iteration: ignore the corresponding meta data at the
929      * end and try again without it */
930     left -= sizeof (struct MetaDataEntry);
931     left -= pos->data_size;
932     if (NULL != pos->plugin_name)
933       left -= strlen (pos->plugin_name) + 1;
934     if (NULL != pos->mime_type)
935       left -= strlen (pos->mime_type) + 1;
936
937     GNUNET_free_non_null (cdata);
938     cdata = NULL;
939
940     i++;
941   }
942   GNUNET_free (ent);
943
944   /* nothing fit, only write header! */
945   ihdr.version = htonl (2);
946   ihdr.entries = htonl (0);
947   ihdr.size = htonl (0);
948   if (NULL == *target)
949     *target = (char *) GNUNET_new (struct MetaDataHeader);
950   memcpy (*target, &ihdr, sizeof (struct MetaDataHeader));
951   return sizeof (struct MetaDataHeader);
952 }
953
954
955 /**
956  * Get the size of the full meta-data in serialized form.
957  *
958  * @param md metadata to inspect
959  * @return number of bytes needed for serialization, -1 on error
960  */
961 ssize_t
962 GNUNET_CONTAINER_meta_data_get_serialized_size (const struct
963                                                 GNUNET_CONTAINER_MetaData *md)
964 {
965   ssize_t ret;
966   char *ptr;
967
968   if (NULL != md->sbuf)
969     return md->sbuf_size;
970   ptr = NULL;
971   ret =
972       GNUNET_CONTAINER_meta_data_serialize (md, &ptr, GNUNET_MAX_MALLOC_CHECKED,
973                                             GNUNET_CONTAINER_META_DATA_SERIALIZE_FULL);
974   if (-1 != ret)
975     GNUNET_free (ptr);
976   return ret;
977 }
978
979
980 /**
981  * Decompress input, return the decompressed data
982  * as output, set outputSize to the number of bytes
983  * that were found.
984  *
985  * @param input compressed data
986  * @param inputSize number of bytes in input
987  * @param outputSize expected size of the output
988  * @return NULL on error
989  */
990 static char *
991 decompress (const char *input, size_t inputSize, size_t outputSize)
992 {
993   char *output;
994   uLongf olen;
995
996   olen = outputSize;
997   output = GNUNET_malloc (olen);
998   if (Z_OK ==
999       uncompress ((Bytef *) output, &olen, (const Bytef *) input, inputSize))
1000     return output;
1001   GNUNET_free (output);
1002   return NULL;
1003 }
1004
1005
1006 /**
1007  * Deserialize meta-data.  Initializes md.
1008  *
1009  * @param input buffer with the serialized metadata
1010  * @param size number of bytes available in input
1011  * @return MD on success, NULL on error (i.e.
1012  *         bad format)
1013  */
1014 struct GNUNET_CONTAINER_MetaData *
1015 GNUNET_CONTAINER_meta_data_deserialize (const char *input, size_t size)
1016 {
1017   struct GNUNET_CONTAINER_MetaData *md;
1018   struct MetaDataHeader hdr;
1019   struct MetaDataEntry ent;
1020   uint32_t ic;
1021   uint32_t i;
1022   char *data;
1023   const char *cdata;
1024   uint32_t version;
1025   uint32_t dataSize;
1026   int compressed;
1027   size_t left;
1028   uint32_t mlen;
1029   uint32_t plen;
1030   uint32_t dlen;
1031   const char *mdata;
1032   const char *meta_data;
1033   const char *plugin_name;
1034   const char *mime_type;
1035   enum EXTRACTOR_MetaFormat format;
1036
1037   if (size < sizeof (struct MetaDataHeader))
1038     return NULL;
1039   memcpy (&hdr, input, sizeof (struct MetaDataHeader));
1040   version = ntohl (hdr.version) & HEADER_VERSION_MASK;
1041   compressed = (ntohl (hdr.version) & HEADER_COMPRESSED) != 0;
1042
1043   if (1 == version)
1044     return NULL;                /* null pointer */
1045   if (2 != version)
1046   {
1047     GNUNET_break_op (0);        /* unsupported version */
1048     return NULL;
1049   }
1050
1051   ic = ntohl (hdr.entries);
1052   dataSize = ntohl (hdr.size);
1053   if ( ((sizeof (struct MetaDataEntry) * ic) > dataSize) ||
1054        ( (0 != ic) &&
1055          (dataSize / ic < sizeof (struct MetaDataEntry)) ) )
1056   {
1057     GNUNET_break_op (0);
1058     return NULL;
1059   }
1060
1061   if (compressed)
1062   {
1063     if (dataSize >= GNUNET_MAX_MALLOC_CHECKED)
1064     {
1065       /* make sure we don't blow our memory limit because of a mal-formed
1066        * message... */
1067       GNUNET_break_op (0);
1068       return NULL;
1069     }
1070     data =
1071         decompress ((const char *) &input[sizeof (struct MetaDataHeader)],
1072                     size - sizeof (struct MetaDataHeader), dataSize);
1073     if (NULL == data)
1074     {
1075       GNUNET_break_op (0);
1076       return NULL;
1077     }
1078     cdata = data;
1079   }
1080   else
1081   {
1082     data = NULL;
1083     cdata = (const char *) &input[sizeof (struct MetaDataHeader)];
1084     if (dataSize != size - sizeof (struct MetaDataHeader))
1085     {
1086       GNUNET_break_op (0);
1087       return NULL;
1088     }
1089   }
1090
1091   md = GNUNET_CONTAINER_meta_data_create ();
1092   left = dataSize - ic * sizeof (struct MetaDataEntry);
1093   mdata = &cdata[ic * sizeof (struct MetaDataEntry)];
1094   for (i = 0; i < ic; i++)
1095   {
1096     memcpy (&ent, &cdata[i * sizeof (struct MetaDataEntry)],
1097             sizeof (struct MetaDataEntry));
1098     format = (enum EXTRACTOR_MetaFormat) ntohl (ent.format);
1099     if ((EXTRACTOR_METAFORMAT_UTF8 != format) &&
1100         (EXTRACTOR_METAFORMAT_C_STRING != format) &&
1101         (EXTRACTOR_METAFORMAT_BINARY != format))
1102     {
1103       GNUNET_break_op (0);
1104       break;
1105     }
1106     dlen = ntohl (ent.data_size);
1107     plen = ntohl (ent.plugin_name_len);
1108     mlen = ntohl (ent.mime_type_len);
1109     if (dlen > left)
1110     {
1111       GNUNET_break_op (0);
1112       break;
1113     }
1114     left -= dlen;
1115     meta_data = &mdata[left];
1116     if ((EXTRACTOR_METAFORMAT_UTF8 == format) ||
1117         (EXTRACTOR_METAFORMAT_C_STRING == format))
1118     {
1119       if (0 == dlen)
1120       {
1121         GNUNET_break_op (0);
1122         break;
1123       }
1124       if ('\0' != meta_data[dlen - 1])
1125       {
1126         GNUNET_break_op (0);
1127         break;
1128       }
1129     }
1130     if (plen > left)
1131     {
1132       GNUNET_break_op (0);
1133       break;
1134     }
1135     left -= plen;
1136     if ((plen > 0) && ('\0' != mdata[left + plen - 1]))
1137     {
1138       GNUNET_break_op (0);
1139       break;
1140     }
1141     if (0 == plen)
1142       plugin_name = NULL;
1143     else
1144       plugin_name = &mdata[left];
1145
1146     if (mlen > left)
1147     {
1148       GNUNET_break_op (0);
1149       break;
1150     }
1151     left -= mlen;
1152     if ((mlen > 0) && ('\0' != mdata[left + mlen - 1]))
1153     {
1154       GNUNET_break_op (0);
1155       break;
1156     }
1157     if (0 == mlen)
1158       mime_type = NULL;
1159     else
1160       mime_type = &mdata[left];
1161     GNUNET_CONTAINER_meta_data_insert (md, plugin_name,
1162                                        (enum EXTRACTOR_MetaType)
1163                                        ntohl (ent.type), format, mime_type,
1164                                        meta_data, dlen);
1165   }
1166   GNUNET_free_non_null (data);
1167   return md;
1168 }
1169
1170
1171 /* end of container_meta_data.c */