c9ce19114022b4e306b0852dcddd918fdc76f0bf
[oweals/gnunet.git] / src / fs / fs_uri.c
1 /*
2      This file is part of GNUnet.
3      Copyright (C) 2003--2014 GNUnet e.V.
4
5      GNUnet is free software: you can redistribute it and/or modify it
6      under the terms of the GNU Affero General Public License as published
7      by the Free Software Foundation, either version 3 of the License,
8      or (at your 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      Affero General Public License for more details.
14
15      You should have received a copy of the GNU Affero General Public License
16      along with this program.  If not, see <http://www.gnu.org/licenses/>.
17
18      SPDX-License-Identifier: AGPL3.0-or-later
19  */
20
21 /**
22  * @file fs/fs_uri.c
23  * @brief Parses and produces uri strings.
24  * @author Igor Wronsky, Christian Grothoff
25  *
26  * GNUnet URIs are of the general form "gnunet://MODULE/IDENTIFIER".
27  * The specific structure of "IDENTIFIER" depends on the module and
28  * maybe differenciated into additional subcategories if applicable.
29  * This module only deals with fs identifiers (MODULE = "fs").
30  * <p>
31  *
32  * This module only parses URIs for the AFS module.  The FS URIs fall
33  * into four categories, "chk", "sks", "ksk" and "loc".  The first three
34  * categories were named in analogy (!) to Freenet, but they do NOT
35  * work in exactly the same way.  They are very similar from the user's
36  * point of view (unique file identifier, subspace, keyword), but the
37  * implementation is rather different in pretty much every detail.
38  * The concrete URI formats are:
39  *
40  * </p>
41  *
42  * <ul><li>
43  *
44  * First, there are URIs that identify a file.  They have the format
45  * "gnunet://fs/chk/HEX1.HEX2.SIZE".  These URIs can be used to
46  * download the file.  The description, filename, mime-type and other
47  * meta-data is NOT part of the file-URI since a URI uniquely
48  * identifies a resource (and the contents of the file would be the
49  * same even if it had a different description).
50  *
51  * </li><li>
52  *
53  * The second category identifies entries in a namespace.  The format
54  * is "gnunet://fs/sks/NAMESPACE/IDENTIFIER" where the namespace
55  * should be given in HEX.  Applications may allow using a nickname
56  * for the namespace if the nickname is not ambiguous.  The identifier
57  * can be either an ASCII sequence or a HEX-encoding.  If the
58  * identifier is in ASCII but the format is ambiguous and could denote
59  * a HEX-string a "/" is appended to indicate ASCII encoding.
60  *
61  * </li> <li>
62  *
63  * The third category identifies ordinary searches.  The format is
64  * "gnunet://fs/ksk/KEYWORD[+KEYWORD]*".  Using the "+" syntax
65  * it is possible to encode searches with the boolean "AND" operator.
66  * "+" is used since it indicates a commutative 'and' operation and
67  * is unlikely to be used in a keyword by itself.
68  *
69  * </li><li>
70  *
71  * The last category identifies a datum on a specific machine.  The
72  * format is "gnunet://fs/loc/HEX1.HEX2.SIZE.PEER.SIG.EXPTIME".  PEER is
73  * the BinName of the public key of the peer storing the datum.  The
74  * signature (SIG) certifies that this peer has this content.
75  * HEX1, HEX2 and SIZE correspond to a 'chk' URI.
76  *
77  * </li></ul>
78  *
79  * The encoding for hexadecimal values is defined in the hashing.c
80  * module in the gnunetutil library and discussed there.
81  *
82  */
83 #include "platform.h"
84 #include "gnunet_fs_service.h"
85 #include "gnunet_signatures.h"
86 #include "fs_api.h"
87 #include <unitypes.h>
88 #include <unicase.h>
89 #include <uniconv.h>
90 #include <unistr.h>
91 #include <unistdio.h>
92
93
94 /**
95  * Get a unique key from a URI.  This is for putting URIs
96  * into HashMaps.  The key may change between FS implementations.
97  *
98  * @param uri uri to convert to a unique key
99  * @param key where to store the unique key
100  * @return #GNUNET_OK on success
101  */
102 int
103 GNUNET_FS_uri_to_key(const struct GNUNET_FS_Uri *uri,
104                      struct GNUNET_HashCode *key)
105 {
106   switch (uri->type)
107     {
108     case GNUNET_FS_URI_CHK:
109       *key = uri->data.chk.chk.query;
110       return GNUNET_OK;
111
112     case GNUNET_FS_URI_SKS:
113       GNUNET_CRYPTO_hash(uri->data.sks.identifier,
114                          strlen(uri->data.sks.identifier),
115                          key);
116       return GNUNET_OK;
117
118     case GNUNET_FS_URI_KSK:
119       if (uri->data.ksk.keywordCount > 0)
120         {
121           GNUNET_CRYPTO_hash(uri->data.ksk.keywords[0],
122                              strlen(uri->data.ksk.keywords[0]),
123                              key);
124           return GNUNET_OK;
125         }
126       else
127         {
128           memset(key, 0, sizeof(struct GNUNET_HashCode));
129           return GNUNET_SYSERR;
130         }
131       break;
132
133     case GNUNET_FS_URI_LOC:
134       GNUNET_CRYPTO_hash(&uri->data.loc.fi,
135                          sizeof(struct FileIdentifier) +
136                          sizeof(struct GNUNET_PeerIdentity),
137                          key);
138       return GNUNET_OK;
139
140     default:
141       memset(key, 0, sizeof(struct GNUNET_HashCode));
142       return GNUNET_SYSERR;
143     }
144 }
145
146
147 /**
148  * Convert keyword URI to a human readable format
149  * (i.e. the search query that was used in the first place)
150  *
151  * @param uri ksk uri to convert to a string
152  * @return string with the keywords
153  */
154 char *
155 GNUNET_FS_uri_ksk_to_string_fancy(const struct GNUNET_FS_Uri *uri)
156 {
157   size_t n;
158   char *ret;
159   unsigned int i;
160   const char *keyword;
161   char **keywords;
162   unsigned int keywordCount;
163
164   if ((NULL == uri) || (GNUNET_FS_URI_KSK != uri->type))
165     {
166       GNUNET_break(0);
167       return NULL;
168     }
169   keywords = uri->data.ksk.keywords;
170   keywordCount = uri->data.ksk.keywordCount;
171   n = keywordCount + 1;
172   for (i = 0; i < keywordCount; i++)
173     {
174       keyword = keywords[i];
175       n += strlen(keyword) - 1;
176       if (NULL != strstr(&keyword[1], " "))
177         n += 2;
178       if (keyword[0] == '+')
179         n++;
180     }
181   ret = GNUNET_malloc(n);
182   strcpy(ret, "");
183   for (i = 0; i < keywordCount; i++)
184     {
185       keyword = keywords[i];
186       if (NULL != strstr(&keyword[1], " "))
187         {
188           strcat(ret, "\"");
189           if (keyword[0] == '+')
190             strcat(ret, keyword);
191           else
192             strcat(ret, &keyword[1]);
193           strcat(ret, "\"");
194         }
195       else
196         {
197           if (keyword[0] == '+')
198             strcat(ret, keyword);
199           else
200             strcat(ret, &keyword[1]);
201         }
202       strcat(ret, " ");
203     }
204   return ret;
205 }
206
207
208 /**
209  * Given a keyword with %-encoding (and possibly quotes to protect
210  * spaces), return a copy of the keyword without %-encoding and
211  * without double-quotes (%22).  Also, add a space at the beginning
212  * if there is not a '+'.
213  *
214  * @param in string with %-encoding
215  * @param emsg where to store the parser error message (if any)
216  * @return decodded string with leading space (or preserved plus)
217  */
218 static char *
219 percent_decode_keyword(const char *in, char **emsg)
220 {
221   char *out;
222   char *ret;
223   unsigned int rpos;
224   unsigned int wpos;
225   unsigned int hx;
226
227   out = GNUNET_strdup(in);
228   rpos = 0;
229   wpos = 0;
230   while (out[rpos] != '\0')
231     {
232       if (out[rpos] == '%')
233         {
234           if (1 != sscanf(&out[rpos + 1], "%2X", &hx))
235             {
236               GNUNET_free(out);
237               *emsg = GNUNET_strdup(
238                 _(/* xgettext:no-c-format */
239                   "Malformed KSK URI (`%' must be followed by HEX number)"));
240               return NULL;
241             }
242           rpos += 3;
243           if (hx == '"')
244             continue; /* skip double quote */
245           out[wpos++] = (char)hx;
246         }
247       else
248         {
249           out[wpos++] = out[rpos++];
250         }
251     }
252   out[wpos] = '\0';
253   if (out[0] == '+')
254     {
255       ret = GNUNET_strdup(out);
256     }
257   else
258     {
259       /* need to prefix with space */
260       ret = GNUNET_malloc(strlen(out) + 2);
261       strcpy(ret, " ");
262       strcat(ret, out);
263     }
264   GNUNET_free(out);
265   return ret;
266 }
267
268 #define GNUNET_FS_URI_KSK_PREFIX GNUNET_FS_URI_PREFIX GNUNET_FS_URI_KSK_INFIX
269
270 /**
271  * Parse a KSK URI.
272  *
273  * @param s an uri string
274  * @param emsg where to store the parser error message (if any)
275  * @return NULL on error, otherwise the KSK URI
276  */
277 static struct GNUNET_FS_Uri *
278 uri_ksk_parse(const char *s, char **emsg)
279 {
280   struct GNUNET_FS_Uri *ret;
281   char **keywords;
282   unsigned int pos;
283   int max;
284   int iret;
285   int i;
286   size_t slen;
287   char *dup;
288   int saw_quote;
289
290   slen = strlen(s);
291   pos = strlen(GNUNET_FS_URI_KSK_PREFIX);
292   if ((slen <= pos) || (0 != strncmp(s, GNUNET_FS_URI_KSK_PREFIX, pos)))
293     return NULL; /* not KSK URI */
294   if ((s[slen - 1] == '+') || (s[pos] == '+'))
295     {
296       *emsg =
297         GNUNET_strdup(_("Malformed KSK URI (must not begin or end with `+')"));
298       return NULL;
299     }
300   max = 1;
301   saw_quote = 0;
302   for (i = pos; i < slen; i++)
303     {
304       if ((s[i] == '%') && (&s[i] == strstr(&s[i], "%22")))
305         {
306           saw_quote = (saw_quote + 1) % 2;
307           i += 3;
308           continue;
309         }
310       if ((s[i] == '+') && (saw_quote == 0))
311         {
312           max++;
313           if (s[i - 1] == '+')
314             {
315               *emsg = GNUNET_strdup(_("Malformed KSK URI (`++' not allowed)"));
316               return NULL;
317             }
318         }
319     }
320   if (saw_quote == 1)
321     {
322       *emsg = GNUNET_strdup(_("Malformed KSK URI (quotes not balanced)"));
323       return NULL;
324     }
325   iret = max;
326   dup = GNUNET_strdup(s);
327   keywords = GNUNET_new_array(max, char *);
328   for (i = slen - 1; i >= (int)pos; i--)
329     {
330       if ((s[i] == '%') && (&s[i] == strstr(&s[i], "%22")))
331         {
332           saw_quote = (saw_quote + 1) % 2;
333           continue;
334         }
335       if ((dup[i] == '+') && (saw_quote == 0))
336         {
337           keywords[--max] = percent_decode_keyword(&dup[i + 1], emsg);
338           if (NULL == keywords[max])
339             goto CLEANUP;
340           dup[i] = '\0';
341         }
342     }
343   keywords[--max] = percent_decode_keyword(&dup[pos], emsg);
344   if (NULL == keywords[max])
345     goto CLEANUP;
346   GNUNET_assert(0 == max);
347   GNUNET_free(dup);
348   ret = GNUNET_new(struct GNUNET_FS_Uri);
349   ret->type = GNUNET_FS_URI_KSK;
350   ret->data.ksk.keywordCount = iret;
351   ret->data.ksk.keywords = keywords;
352   return ret;
353 CLEANUP:
354   for (i = 0; i < max; i++)
355     GNUNET_free_non_null(keywords[i]);
356   GNUNET_free(keywords);
357   GNUNET_free(dup);
358   return NULL;
359 }
360
361
362 #define GNUNET_FS_URI_SKS_PREFIX GNUNET_FS_URI_PREFIX GNUNET_FS_URI_SKS_INFIX
363
364 /**
365  * Parse an SKS URI.
366  *
367  * @param s an uri string
368  * @param emsg where to store the parser error message (if any)
369  * @return NULL on error, SKS URI otherwise
370  */
371 static struct GNUNET_FS_Uri *
372 uri_sks_parse(const char *s, char **emsg)
373 {
374   struct GNUNET_FS_Uri *ret;
375   struct GNUNET_CRYPTO_EcdsaPublicKey ns;
376   size_t pos;
377   char *end;
378
379   pos = strlen(GNUNET_FS_URI_SKS_PREFIX);
380   if ((strlen(s) <= pos) || (0 != strncmp(s, GNUNET_FS_URI_SKS_PREFIX, pos)))
381     return NULL; /* not an SKS URI */
382   end = strchr(&s[pos], '/');
383   if ((NULL == end) ||
384       (GNUNET_OK != GNUNET_STRINGS_string_to_data(&s[pos],
385                                                   end - &s[pos],
386                                                   &ns,
387                                                   sizeof(ns))))
388     {
389       *emsg = GNUNET_strdup(_("Malformed SKS URI (wrong syntax)"));
390       return NULL; /* malformed */
391     }
392   end++; /* skip over '/' */
393   ret = GNUNET_new(struct GNUNET_FS_Uri);
394   ret->type = GNUNET_FS_URI_SKS;
395   ret->data.sks.ns = ns;
396   ret->data.sks.identifier = GNUNET_strdup(end);
397   return ret;
398 }
399
400 #define GNUNET_FS_URI_CHK_PREFIX GNUNET_FS_URI_PREFIX GNUNET_FS_URI_CHK_INFIX
401
402
403 /**
404  * Parse a CHK URI.
405  *
406  * @param s an uri string
407  * @param emsg where to store the parser error message (if any)
408  * @return NULL on error, CHK URI otherwise
409  */
410 static struct GNUNET_FS_Uri *
411 uri_chk_parse(const char *s, char **emsg)
412 {
413   struct GNUNET_FS_Uri *ret;
414   struct FileIdentifier fi;
415   unsigned int pos;
416   unsigned long long flen;
417   size_t slen;
418   char h1[sizeof(struct GNUNET_CRYPTO_HashAsciiEncoded)];
419   char h2[sizeof(struct GNUNET_CRYPTO_HashAsciiEncoded)];
420
421   slen = strlen(s);
422   pos = strlen(GNUNET_FS_URI_CHK_PREFIX);
423   if ((slen < pos + 2 * sizeof(struct GNUNET_CRYPTO_HashAsciiEncoded) + 1) ||
424       (0 != strncmp(s, GNUNET_FS_URI_CHK_PREFIX, pos)))
425     return NULL; /* not a CHK URI */
426   if ((s[pos + sizeof(struct GNUNET_CRYPTO_HashAsciiEncoded) - 1] != '.') ||
427       (s[pos + sizeof(struct GNUNET_CRYPTO_HashAsciiEncoded) * 2 - 1] != '.'))
428     {
429       *emsg = GNUNET_strdup(_("Malformed CHK URI (wrong syntax)"));
430       return NULL;
431     }
432   GNUNET_memcpy(h1, &s[pos], sizeof(struct GNUNET_CRYPTO_HashAsciiEncoded));
433   h1[sizeof(struct GNUNET_CRYPTO_HashAsciiEncoded) - 1] = '\0';
434   GNUNET_memcpy(h2,
435                 &s[pos + sizeof(struct GNUNET_CRYPTO_HashAsciiEncoded)],
436                 sizeof(struct GNUNET_CRYPTO_HashAsciiEncoded));
437   h2[sizeof(struct GNUNET_CRYPTO_HashAsciiEncoded) - 1] = '\0';
438
439   if ((GNUNET_OK != GNUNET_CRYPTO_hash_from_string(h1, &fi.chk.key)) ||
440       (GNUNET_OK != GNUNET_CRYPTO_hash_from_string(h2, &fi.chk.query)) ||
441       (1 !=
442        sscanf(&s[pos + sizeof(struct GNUNET_CRYPTO_HashAsciiEncoded) * 2],
443               "%llu",
444               &flen)))
445     {
446       *emsg = GNUNET_strdup(_("Malformed CHK URI (failed to decode CHK)"));
447       return NULL;
448     }
449   fi.file_length = GNUNET_htonll(flen);
450   ret = GNUNET_new(struct GNUNET_FS_Uri);
451   ret->type = GNUNET_FS_URI_CHK;
452   ret->data.chk = fi;
453   return ret;
454 }
455
456
457 GNUNET_NETWORK_STRUCT_BEGIN
458 /**
459  * Structure that defines how the contents of a location URI must be
460  * assembled in memory to create or verify the signature of a location
461  * URI.
462  */
463 struct LocUriAssembly {
464   /**
465    * What is being signed (rest of this struct).
466    */
467   struct GNUNET_CRYPTO_EccSignaturePurpose purpose;
468
469   /**
470    * Expiration time of the offer.
471    */
472   struct GNUNET_TIME_AbsoluteNBO exptime;
473
474   /**
475    * File being offered.
476    */
477   struct FileIdentifier fi;
478
479   /**
480    * Peer offering the file.
481    */
482   struct GNUNET_PeerIdentity peer;
483 };
484 GNUNET_NETWORK_STRUCT_END
485
486
487 #define GNUNET_FS_URI_LOC_PREFIX GNUNET_FS_URI_PREFIX GNUNET_FS_URI_LOC_INFIX
488
489 #define SIGNATURE_ASCII_LENGTH 103
490
491 /**
492  * Parse a LOC URI.
493  * Also verifies validity of the location URI.
494  *
495  * @param s an uri string
496  * @param emsg where to store the parser error message (if any)
497  * @return NULL on error, valid LOC URI otherwise
498  */
499 static struct GNUNET_FS_Uri *
500 uri_loc_parse(const char *s, char **emsg)
501 {
502   struct GNUNET_FS_Uri *uri;
503   char h1[sizeof(struct GNUNET_CRYPTO_HashAsciiEncoded)];
504   char h2[sizeof(struct GNUNET_CRYPTO_HashAsciiEncoded)];
505   unsigned int pos;
506   unsigned int npos;
507   unsigned long long exptime;
508   unsigned long long flen;
509   struct GNUNET_TIME_Absolute et;
510   struct GNUNET_CRYPTO_EddsaSignature sig;
511   struct LocUriAssembly ass;
512   size_t slen;
513
514   slen = strlen(s);
515   pos = strlen(GNUNET_FS_URI_LOC_PREFIX);
516   if ((slen < pos + 2 * sizeof(struct GNUNET_CRYPTO_HashAsciiEncoded) + 1) ||
517       (0 != strncmp(s, GNUNET_FS_URI_LOC_PREFIX, pos)))
518     return NULL; /* not a LOC URI */
519   if ((s[pos + sizeof(struct GNUNET_CRYPTO_HashAsciiEncoded) - 1] != '.') ||
520       (s[pos + sizeof(struct GNUNET_CRYPTO_HashAsciiEncoded) * 2 - 1] != '.'))
521     {
522       *emsg = GNUNET_strdup(_("LOC URI malformed (wrong syntax)"));
523       return NULL;
524     }
525   GNUNET_memcpy(h1, &s[pos], sizeof(struct GNUNET_CRYPTO_HashAsciiEncoded));
526   h1[sizeof(struct GNUNET_CRYPTO_HashAsciiEncoded) - 1] = '\0';
527   GNUNET_memcpy(h2,
528                 &s[pos + sizeof(struct GNUNET_CRYPTO_HashAsciiEncoded)],
529                 sizeof(struct GNUNET_CRYPTO_HashAsciiEncoded));
530   h2[sizeof(struct GNUNET_CRYPTO_HashAsciiEncoded) - 1] = '\0';
531
532   if ((GNUNET_OK != GNUNET_CRYPTO_hash_from_string(h1, &ass.fi.chk.key)) ||
533       (GNUNET_OK != GNUNET_CRYPTO_hash_from_string(h2, &ass.fi.chk.query)) ||
534       (1 !=
535        sscanf(&s[pos + sizeof(struct GNUNET_CRYPTO_HashAsciiEncoded) * 2],
536               "%llu",
537               &flen)))
538     {
539       *emsg = GNUNET_strdup(_("LOC URI malformed (no CHK)"));
540       return NULL;
541     }
542   ass.fi.file_length = GNUNET_htonll(flen);
543
544   npos = pos + sizeof(struct GNUNET_CRYPTO_HashAsciiEncoded) * 2;
545   while ((s[npos] != '\0') && (s[npos] != '.'))
546     npos++;
547   if (s[npos] == '\0')
548     {
549       *emsg = GNUNET_strdup(_("LOC URI malformed (missing LOC)"));
550       goto ERR;
551     }
552   npos++;
553   if ((strlen(&s[npos]) <= GNUNET_CRYPTO_PKEY_ASCII_LENGTH + 1) ||
554       ('.' != s[npos + GNUNET_CRYPTO_PKEY_ASCII_LENGTH]))
555     {
556       *emsg =
557         GNUNET_strdup(_("LOC URI malformed (wrong syntax for public key)"));
558     }
559   if (
560     GNUNET_OK !=
561     GNUNET_CRYPTO_eddsa_public_key_from_string(&s[npos],
562                                                GNUNET_CRYPTO_PKEY_ASCII_LENGTH,
563                                                &ass.peer.public_key))
564     {
565       *emsg =
566         GNUNET_strdup(_("LOC URI malformed (could not decode public key)"));
567       goto ERR;
568     }
569   npos += GNUNET_CRYPTO_PKEY_ASCII_LENGTH;
570   if (s[npos++] != '.')
571     {
572       *emsg = GNUNET_strdup(_("LOC URI malformed (could not find signature)"));
573       goto ERR;
574     }
575   if ((strlen(&s[npos]) <= SIGNATURE_ASCII_LENGTH + 1) ||
576       ('.' != s[npos + SIGNATURE_ASCII_LENGTH]))
577     {
578       *emsg =
579         GNUNET_strdup(_("LOC URI malformed (wrong syntax for signature)"));
580       goto ERR;
581     }
582   if (GNUNET_OK !=
583       GNUNET_STRINGS_string_to_data(&s[npos],
584                                     SIGNATURE_ASCII_LENGTH,
585                                     &sig,
586                                     sizeof(
587                                       struct GNUNET_CRYPTO_EddsaSignature)))
588     {
589       *emsg =
590         GNUNET_strdup(_("LOC URI malformed (could not decode signature)"));
591       goto ERR;
592     }
593   npos += SIGNATURE_ASCII_LENGTH;
594   if (s[npos++] != '.')
595     {
596       *emsg = GNUNET_strdup(
597         _("LOC URI malformed (wrong syntax for expiration time)"));
598       goto ERR;
599     }
600   if (1 != sscanf(&s[npos], "%llu", &exptime))
601     {
602       *emsg =
603         GNUNET_strdup(_("LOC URI malformed (could not parse expiration time)"));
604       goto ERR;
605     }
606   ass.purpose.size = htonl(sizeof(struct LocUriAssembly));
607   ass.purpose.purpose = htonl(GNUNET_SIGNATURE_PURPOSE_PEER_PLACEMENT);
608   et.abs_value_us = exptime * 1000LL * 1000LL;
609   ass.exptime = GNUNET_TIME_absolute_hton(et);
610   if (GNUNET_OK !=
611       GNUNET_CRYPTO_eddsa_verify(GNUNET_SIGNATURE_PURPOSE_PEER_PLACEMENT,
612                                  &ass.purpose,
613                                  &sig,
614                                  &ass.peer.public_key))
615     {
616       *emsg =
617         GNUNET_strdup(_("LOC URI malformed (signature failed validation)"));
618       goto ERR;
619     }
620   uri = GNUNET_new(struct GNUNET_FS_Uri);
621   uri->type = GNUNET_FS_URI_LOC;
622   uri->data.loc.fi = ass.fi;
623   uri->data.loc.peer = ass.peer;
624   uri->data.loc.expirationTime = et;
625   uri->data.loc.contentSignature = sig;
626
627   return uri;
628 ERR:
629   return NULL;
630 }
631
632
633 /**
634  * Convert a UTF-8 String to a URI.
635  *
636  * @param uri string to parse
637  * @param emsg where to store the parser error message (if any)
638  * @return NULL on error
639  */
640 struct GNUNET_FS_Uri *
641 GNUNET_FS_uri_parse(const char *uri, char **emsg)
642 {
643   struct GNUNET_FS_Uri *ret;
644   char *msg;
645
646   if (NULL == uri)
647     {
648       GNUNET_break(0);
649       if (NULL != emsg)
650         *emsg = GNUNET_strdup(_("invalid argument"));
651       return NULL;
652     }
653   if (NULL == emsg)
654     emsg = &msg;
655   *emsg = NULL;
656   if ((NULL != (ret = uri_chk_parse(uri, emsg))) ||
657       (NULL != (ret = uri_ksk_parse(uri, emsg))) ||
658       (NULL != (ret = uri_sks_parse(uri, emsg))) ||
659       (NULL != (ret = uri_loc_parse(uri, emsg))))
660     return ret;
661   if (NULL == *emsg)
662     *emsg = GNUNET_strdup(_("Unrecognized URI type"));
663   if (emsg == &msg)
664     GNUNET_free(msg);
665   return NULL;
666 }
667
668
669 /**
670  * Free URI.
671  *
672  * @param uri uri to free
673  */
674 void
675 GNUNET_FS_uri_destroy(struct GNUNET_FS_Uri *uri)
676 {
677   unsigned int i;
678
679   switch (uri->type)
680     {
681     case GNUNET_FS_URI_KSK:
682       for (i = 0; i < uri->data.ksk.keywordCount; i++)
683         GNUNET_free(uri->data.ksk.keywords[i]);
684       GNUNET_array_grow(uri->data.ksk.keywords, uri->data.ksk.keywordCount, 0);
685       break;
686
687     case GNUNET_FS_URI_SKS:
688       GNUNET_free(uri->data.sks.identifier);
689       break;
690
691     case GNUNET_FS_URI_LOC:
692       break;
693
694     default:
695       /* do nothing */
696       break;
697     }
698   GNUNET_free(uri);
699 }
700
701
702 /**
703  * How many keywords are ANDed in this keyword URI?
704  *
705  * @param uri ksk uri to get the number of keywords from
706  * @return 0 if this is not a keyword URI
707  */
708 unsigned int
709 GNUNET_FS_uri_ksk_get_keyword_count(const struct GNUNET_FS_Uri *uri)
710 {
711   if (uri->type != GNUNET_FS_URI_KSK)
712     return 0;
713   return uri->data.ksk.keywordCount;
714 }
715
716
717 /**
718  * Iterate over all keywords in this keyword URI.
719  *
720  * @param uri ksk uri to get the keywords from
721  * @param iterator function to call on each keyword
722  * @param iterator_cls closure for iterator
723  * @return -1 if this is not a keyword URI, otherwise number of
724  *   keywords iterated over until iterator aborted
725  */
726 int
727 GNUNET_FS_uri_ksk_get_keywords(const struct GNUNET_FS_Uri *uri,
728                                GNUNET_FS_KeywordIterator iterator,
729                                void *iterator_cls)
730 {
731   unsigned int i;
732   char *keyword;
733
734   if (uri->type != GNUNET_FS_URI_KSK)
735     return -1;
736   if (NULL == iterator)
737     return uri->data.ksk.keywordCount;
738   for (i = 0; i < uri->data.ksk.keywordCount; i++)
739     {
740       keyword = uri->data.ksk.keywords[i];
741       /* first character of keyword indicates
742        * if it is mandatory or not */
743       if (GNUNET_OK != iterator(iterator_cls, &keyword[1], keyword[0] == '+'))
744         return i;
745     }
746   return i;
747 }
748
749
750 /**
751  * Add the given keyword to the set of keywords represented by the URI.
752  * Does nothing if the keyword is already present.
753  *
754  * @param uri ksk uri to modify
755  * @param keyword keyword to add
756  * @param is_mandatory is this keyword mandatory?
757  */
758 void
759 GNUNET_FS_uri_ksk_add_keyword(struct GNUNET_FS_Uri *uri,
760                               const char *keyword,
761                               int is_mandatory)
762 {
763   unsigned int i;
764   const char *old;
765   char *n;
766
767   GNUNET_assert(uri->type == GNUNET_FS_URI_KSK);
768   for (i = 0; i < uri->data.ksk.keywordCount; i++)
769     {
770       old = uri->data.ksk.keywords[i];
771       if (0 == strcmp(&old[1], keyword))
772         return;
773     }
774   GNUNET_asprintf(&n, is_mandatory ? "+%s" : " %s", keyword);
775   GNUNET_array_append(uri->data.ksk.keywords, uri->data.ksk.keywordCount, n);
776 }
777
778
779 /**
780  * Remove the given keyword from the set of keywords represented by the URI.
781  * Does nothing if the keyword is not present.
782  *
783  * @param uri ksk uri to modify
784  * @param keyword keyword to add
785  */
786 void
787 GNUNET_FS_uri_ksk_remove_keyword(struct GNUNET_FS_Uri *uri,
788                                  const char *keyword)
789 {
790   unsigned int i;
791   char *old;
792
793   GNUNET_assert(uri->type == GNUNET_FS_URI_KSK);
794   for (i = 0; i < uri->data.ksk.keywordCount; i++)
795     {
796       old = uri->data.ksk.keywords[i];
797       if (0 == strcmp(&old[1], keyword))
798         {
799           uri->data.ksk.keywords[i] =
800             uri->data.ksk.keywords[uri->data.ksk.keywordCount - 1];
801           GNUNET_array_grow(uri->data.ksk.keywords,
802                             uri->data.ksk.keywordCount,
803                             uri->data.ksk.keywordCount - 1);
804           GNUNET_free(old);
805           return;
806         }
807     }
808 }
809
810
811 /**
812  * Obtain the identity of the peer offering the data
813  *
814  * @param uri the location URI to inspect
815  * @param peer where to store the identify of the peer (presumably) offering the content
816  * @return #GNUNET_SYSERR if this is not a location URI, otherwise #GNUNET_OK
817  */
818 int
819 GNUNET_FS_uri_loc_get_peer_identity(const struct GNUNET_FS_Uri *uri,
820                                     struct GNUNET_PeerIdentity *peer)
821 {
822   if (uri->type != GNUNET_FS_URI_LOC)
823     return GNUNET_SYSERR;
824   *peer = uri->data.loc.peer;
825   return GNUNET_OK;
826 }
827
828
829 /**
830  * Obtain the expiration of the LOC URI.
831  *
832  * @param uri location URI to get the expiration from
833  * @return expiration time of the URI
834  */
835 struct GNUNET_TIME_Absolute
836 GNUNET_FS_uri_loc_get_expiration(const struct GNUNET_FS_Uri *uri)
837 {
838   GNUNET_assert(uri->type == GNUNET_FS_URI_LOC);
839   return uri->data.loc.expirationTime;
840 }
841
842
843 /**
844  * Obtain the URI of the content itself.
845  *
846  * @param uri location URI to get the content URI from
847  * @return NULL if argument is not a location URI
848  */
849 struct GNUNET_FS_Uri *
850 GNUNET_FS_uri_loc_get_uri(const struct GNUNET_FS_Uri *uri)
851 {
852   struct GNUNET_FS_Uri *ret;
853
854   if (uri->type != GNUNET_FS_URI_LOC)
855     return NULL;
856   ret = GNUNET_new(struct GNUNET_FS_Uri);
857   ret->type = GNUNET_FS_URI_CHK;
858   ret->data.chk = uri->data.loc.fi;
859   return ret;
860 }
861
862
863 /**
864  * Construct a location URI (this peer will be used for the location).
865  * This function should only be called from within gnunet-service-fs,
866  * as it requires the peer's private key which is generally unavailable
867  * to processes directly under the user's control.  However, for
868  * testing and as it logically fits under URIs, it is in this API.
869  *
870  * @param base_uri content offered by the sender
871  * @param sign_key private key of the peer
872  * @param expiration_time how long will the content be offered?
873  * @return the location URI, NULL on error
874  */
875 struct GNUNET_FS_Uri *
876 GNUNET_FS_uri_loc_create(const struct GNUNET_FS_Uri *base_uri,
877                          const struct GNUNET_CRYPTO_EddsaPrivateKey *sign_key,
878                          struct GNUNET_TIME_Absolute expiration_time)
879 {
880   struct GNUNET_FS_Uri *uri;
881   struct GNUNET_CRYPTO_EddsaPublicKey my_public_key;
882   struct LocUriAssembly ass;
883   struct GNUNET_TIME_Absolute et;
884
885   if (GNUNET_FS_URI_CHK != base_uri->type)
886     return NULL;
887   /* we round expiration time to full seconds for SKS URIs */
888   et.abs_value_us = (expiration_time.abs_value_us / 1000000LL) * 1000000LL;
889   GNUNET_CRYPTO_eddsa_key_get_public(sign_key, &my_public_key);
890   ass.purpose.size = htonl(sizeof(struct LocUriAssembly));
891   ass.purpose.purpose = htonl(GNUNET_SIGNATURE_PURPOSE_PEER_PLACEMENT);
892   ass.exptime = GNUNET_TIME_absolute_hton(et);
893   ass.fi = base_uri->data.chk;
894   ass.peer.public_key = my_public_key;
895   uri = GNUNET_new(struct GNUNET_FS_Uri);
896   uri->type = GNUNET_FS_URI_LOC;
897   uri->data.loc.fi = base_uri->data.chk;
898   uri->data.loc.expirationTime = et;
899   uri->data.loc.peer.public_key = my_public_key;
900   GNUNET_assert(GNUNET_OK ==
901                 GNUNET_CRYPTO_eddsa_sign(sign_key,
902                                          &ass.purpose,
903                                          &uri->data.loc.contentSignature));
904   return uri;
905 }
906
907
908 /**
909  * Create an SKS URI from a namespace ID and an identifier.
910  *
911  * @param ns namespace ID
912  * @param id identifier
913  * @return an FS URI for the given namespace and identifier
914  */
915 struct GNUNET_FS_Uri *
916 GNUNET_FS_uri_sks_create(const struct GNUNET_CRYPTO_EcdsaPublicKey *ns,
917                          const char *id)
918 {
919   struct GNUNET_FS_Uri *ns_uri;
920
921   ns_uri = GNUNET_new(struct GNUNET_FS_Uri);
922   ns_uri->type = GNUNET_FS_URI_SKS;
923   ns_uri->data.sks.ns = *ns;
924   ns_uri->data.sks.identifier = GNUNET_strdup(id);
925   return ns_uri;
926 }
927
928
929 /**
930  * Merge the sets of keywords from two KSK URIs.
931  * (useful for merging the canonicalized keywords with
932  * the original keywords for sharing).
933  *
934  * @param u1 first uri
935  * @param u2 second uri
936  * @return merged URI, NULL on error
937  */
938 struct GNUNET_FS_Uri *
939 GNUNET_FS_uri_ksk_merge(const struct GNUNET_FS_Uri *u1,
940                         const struct GNUNET_FS_Uri *u2)
941 {
942   struct GNUNET_FS_Uri *ret;
943   unsigned int kc;
944   unsigned int i;
945   unsigned int j;
946   int found;
947   const char *kp;
948   char **kl;
949
950   if ((u1 == NULL) && (u2 == NULL))
951     return NULL;
952   if (u1 == NULL)
953     return GNUNET_FS_uri_dup(u2);
954   if (u2 == NULL)
955     return GNUNET_FS_uri_dup(u1);
956   if ((u1->type != GNUNET_FS_URI_KSK) || (u2->type != GNUNET_FS_URI_KSK))
957     {
958       GNUNET_break(0);
959       return NULL;
960     }
961   kc = u1->data.ksk.keywordCount;
962   kl = GNUNET_new_array(kc + u2->data.ksk.keywordCount, char *);
963   for (i = 0; i < u1->data.ksk.keywordCount; i++)
964     kl[i] = GNUNET_strdup(u1->data.ksk.keywords[i]);
965   for (i = 0; i < u2->data.ksk.keywordCount; i++)
966     {
967       kp = u2->data.ksk.keywords[i];
968       found = 0;
969       for (j = 0; j < u1->data.ksk.keywordCount; j++)
970         if (0 == strcmp(kp + 1, kl[j] + 1))
971           {
972             found = 1;
973             if (kp[0] == '+')
974               kl[j][0] = '+';
975             break;
976           }
977       if (0 == found)
978         kl[kc++] = GNUNET_strdup(kp);
979     }
980   ret = GNUNET_new(struct GNUNET_FS_Uri);
981   ret->type = GNUNET_FS_URI_KSK;
982   ret->data.ksk.keywordCount = kc;
983   ret->data.ksk.keywords = kl;
984   return ret;
985 }
986
987
988 /**
989  * Duplicate URI.
990  *
991  * @param uri the URI to duplicate
992  * @return copy of the URI
993  */
994 struct GNUNET_FS_Uri *
995 GNUNET_FS_uri_dup(const struct GNUNET_FS_Uri *uri)
996 {
997   struct GNUNET_FS_Uri *ret;
998   unsigned int i;
999
1000   if (uri == NULL)
1001     return NULL;
1002   ret = GNUNET_new(struct GNUNET_FS_Uri);
1003   GNUNET_memcpy(ret, uri, sizeof(struct GNUNET_FS_Uri));
1004   switch (ret->type)
1005     {
1006     case GNUNET_FS_URI_KSK:
1007       if (ret->data.ksk.keywordCount >=
1008           GNUNET_MAX_MALLOC_CHECKED / sizeof(char *))
1009         {
1010           GNUNET_break(0);
1011           GNUNET_free(ret);
1012           return NULL;
1013         }
1014       if (ret->data.ksk.keywordCount > 0)
1015         {
1016           ret->data.ksk.keywords =
1017             GNUNET_new_array(ret->data.ksk.keywordCount, char *);
1018           for (i = 0; i < ret->data.ksk.keywordCount; i++)
1019             ret->data.ksk.keywords[i] = GNUNET_strdup(uri->data.ksk.keywords[i]);
1020         }
1021       else
1022         ret->data.ksk.keywords = NULL; /* just to be sure */
1023       break;
1024
1025     case GNUNET_FS_URI_SKS:
1026       ret->data.sks.identifier = GNUNET_strdup(uri->data.sks.identifier);
1027       break;
1028
1029     case GNUNET_FS_URI_LOC:
1030       break;
1031
1032     default:
1033       break;
1034     }
1035   return ret;
1036 }
1037
1038
1039 /**
1040  * Create an FS URI from a single user-supplied string of keywords.
1041  * The string is broken up at spaces into individual keywords.
1042  * Keywords that start with "+" are mandatory.  Double-quotes can
1043  * be used to prevent breaking up strings at spaces (and also
1044  * to specify non-mandatory keywords starting with "+").
1045  *
1046  * Keywords must contain a balanced number of double quotes and
1047  * double quotes can not be used in the actual keywords (for
1048  * example, the string '""foo bar""' will be turned into two
1049  * "OR"ed keywords 'foo' and 'bar', not into '"foo bar"'.
1050  *
1051  * @param keywords the keyword string
1052  * @param emsg where to store an error message
1053  * @return an FS URI for the given keywords, NULL
1054  *  if keywords is not legal (i.e. empty).
1055  */
1056 struct GNUNET_FS_Uri *
1057 GNUNET_FS_uri_ksk_create(const char *keywords, char **emsg)
1058 {
1059   char **keywordarr;
1060   unsigned int num_Words;
1061   int inWord;
1062   char *pos;
1063   struct GNUNET_FS_Uri *uri;
1064   char *searchString;
1065   int saw_quote;
1066
1067   if (keywords == NULL)
1068     {
1069       *emsg = GNUNET_strdup(_("No keywords specified!\n"));
1070       GNUNET_break(0);
1071       return NULL;
1072     }
1073   searchString = GNUNET_strdup(keywords);
1074   num_Words = 0;
1075   inWord = 0;
1076   saw_quote = 0;
1077   pos = searchString;
1078   while ('\0' != *pos)
1079     {
1080       if ((saw_quote == 0) && (isspace((unsigned char)*pos)))
1081         {
1082           inWord = 0;
1083         }
1084       else if (0 == inWord)
1085         {
1086           inWord = 1;
1087           ++num_Words;
1088         }
1089       if ('"' == *pos)
1090         saw_quote = (saw_quote + 1) % 2;
1091       pos++;
1092     }
1093   if (num_Words == 0)
1094     {
1095       GNUNET_free(searchString);
1096       *emsg = GNUNET_strdup(_("No keywords specified!\n"));
1097       return NULL;
1098     }
1099   if (saw_quote != 0)
1100     {
1101       GNUNET_free(searchString);
1102       *emsg = GNUNET_strdup(_("Number of double-quotes not balanced!\n"));
1103       return NULL;
1104     }
1105   keywordarr = GNUNET_new_array(num_Words, char *);
1106   num_Words = 0;
1107   inWord = 0;
1108   pos = searchString;
1109   while ('\0' != *pos)
1110     {
1111       if ((saw_quote == 0) && (isspace((unsigned char)*pos)))
1112         {
1113           inWord = 0;
1114           *pos = '\0';
1115         }
1116       else if (0 == inWord)
1117         {
1118           keywordarr[num_Words] = pos;
1119           inWord = 1;
1120           ++num_Words;
1121         }
1122       if ('"' == *pos)
1123         saw_quote = (saw_quote + 1) % 2;
1124       pos++;
1125     }
1126   uri =
1127     GNUNET_FS_uri_ksk_create_from_args(num_Words, (const char **)keywordarr);
1128   GNUNET_free(keywordarr);
1129   GNUNET_free(searchString);
1130   return uri;
1131 }
1132
1133
1134 /**
1135  * Create an FS URI from a user-supplied command line of keywords.
1136  * Arguments should start with "+" to indicate mandatory
1137  * keywords.
1138  *
1139  * @param argc number of keywords
1140  * @param argv keywords (double quotes are not required for
1141  *             keywords containing spaces; however, double
1142  *             quotes are required for keywords starting with
1143  *             "+"); there is no mechanism for having double
1144  *             quotes in the actual keywords (if the user
1145  *             did specifically specify double quotes, the
1146  *             caller should convert each double quote
1147  *             into two single quotes).
1148  * @return an FS URI for the given keywords, NULL
1149  *  if keywords is not legal (i.e. empty).
1150  */
1151 struct GNUNET_FS_Uri *
1152 GNUNET_FS_uri_ksk_create_from_args(unsigned int argc, const char **argv)
1153 {
1154   unsigned int i;
1155   struct GNUNET_FS_Uri *uri;
1156   const char *keyword;
1157   char *val;
1158   const char *r;
1159   char *w;
1160   char *emsg;
1161
1162   if (argc == 0)
1163     return NULL;
1164   /* allow URI to be given as one and only keyword and
1165    * handle accordingly */
1166   emsg = NULL;
1167   if ((argc == 1) && (strlen(argv[0]) > strlen(GNUNET_FS_URI_PREFIX)) &&
1168       (0 == strncmp(argv[0],
1169                     GNUNET_FS_URI_PREFIX,
1170                     strlen(GNUNET_FS_URI_PREFIX))) &&
1171       (NULL != (uri = GNUNET_FS_uri_parse(argv[0], &emsg))))
1172     return uri;
1173   GNUNET_free_non_null(emsg);
1174   uri = GNUNET_new(struct GNUNET_FS_Uri);
1175   uri->type = GNUNET_FS_URI_KSK;
1176   uri->data.ksk.keywordCount = argc;
1177   uri->data.ksk.keywords = GNUNET_new_array(argc, char *);
1178   for (i = 0; i < argc; i++)
1179     {
1180       keyword = argv[i];
1181       if (keyword[0] == '+')
1182         val = GNUNET_strdup(keyword);
1183       else
1184         GNUNET_asprintf(&val, " %s", keyword);
1185       r = val;
1186       w = val;
1187       while ('\0' != *r)
1188         {
1189           if ('"' == *r)
1190             r++;
1191           else
1192             *(w++) = *(r++);
1193         }
1194       *w = '\0';
1195       uri->data.ksk.keywords[i] = val;
1196     }
1197   return uri;
1198 }
1199
1200
1201 /**
1202  * Test if two URIs are equal.
1203  *
1204  * @param u1 one of the URIs
1205  * @param u2 the other URI
1206  * @return #GNUNET_YES if the URIs are equal
1207  */
1208 int
1209 GNUNET_FS_uri_test_equal(const struct GNUNET_FS_Uri *u1,
1210                          const struct GNUNET_FS_Uri *u2)
1211 {
1212   int ret;
1213   unsigned int i;
1214   unsigned int j;
1215
1216   GNUNET_assert(u1 != NULL);
1217   GNUNET_assert(u2 != NULL);
1218   if (u1->type != u2->type)
1219     return GNUNET_NO;
1220   switch (u1->type)
1221     {
1222     case GNUNET_FS_URI_CHK:
1223       if (0 ==
1224           memcmp(&u1->data.chk, &u2->data.chk, sizeof(struct FileIdentifier)))
1225         return GNUNET_YES;
1226       return GNUNET_NO;
1227
1228     case GNUNET_FS_URI_SKS:
1229       if ((0 == memcmp(&u1->data.sks.ns,
1230                        &u2->data.sks.ns,
1231                        sizeof(struct GNUNET_CRYPTO_EcdsaPublicKey))) &&
1232           (0 == strcmp(u1->data.sks.identifier, u2->data.sks.identifier)))
1233
1234         return GNUNET_YES;
1235       return GNUNET_NO;
1236
1237     case GNUNET_FS_URI_KSK:
1238       if (u1->data.ksk.keywordCount != u2->data.ksk.keywordCount)
1239         return GNUNET_NO;
1240       for (i = 0; i < u1->data.ksk.keywordCount; i++)
1241         {
1242           ret = GNUNET_NO;
1243           for (j = 0; j < u2->data.ksk.keywordCount; j++)
1244             {
1245               if (0 == strcmp(u1->data.ksk.keywords[i], u2->data.ksk.keywords[j]))
1246                 {
1247                   ret = GNUNET_YES;
1248                   break;
1249                 }
1250             }
1251           if (ret == GNUNET_NO)
1252             return GNUNET_NO;
1253         }
1254       return GNUNET_YES;
1255
1256     case GNUNET_FS_URI_LOC:
1257       if (memcmp(&u1->data.loc,
1258                  &u2->data.loc,
1259                  sizeof(struct FileIdentifier) +
1260                  sizeof(struct GNUNET_CRYPTO_EcdsaPublicKey) +
1261                  sizeof(struct GNUNET_TIME_Absolute) +
1262                  sizeof(unsigned short) + sizeof(unsigned short)) != 0)
1263         return GNUNET_NO;
1264       return GNUNET_YES;
1265
1266     default:
1267       return GNUNET_NO;
1268     }
1269 }
1270
1271
1272 /**
1273  * Is this a namespace URI?
1274  *
1275  * @param uri the uri to check
1276  * @return #GNUNET_YES if this is an SKS uri
1277  */
1278 int
1279 GNUNET_FS_uri_test_sks(const struct GNUNET_FS_Uri *uri)
1280 {
1281   return uri->type == GNUNET_FS_URI_SKS;
1282 }
1283
1284
1285 /**
1286  * Get the ID of a namespace from the given
1287  * namespace URI.
1288  *
1289  * @param uri the uri to get the namespace ID from
1290  * @param pseudonym where to store the ID of the namespace
1291  * @return #GNUNET_OK on success
1292  */
1293 int
1294 GNUNET_FS_uri_sks_get_namespace(const struct GNUNET_FS_Uri *uri,
1295                                 struct GNUNET_CRYPTO_EcdsaPublicKey *pseudonym)
1296 {
1297   if (!GNUNET_FS_uri_test_sks(uri))
1298     {
1299       GNUNET_break(0);
1300       return GNUNET_SYSERR;
1301     }
1302   *pseudonym = uri->data.sks.ns;
1303   return GNUNET_OK;
1304 }
1305
1306
1307 /**
1308  * Get the content identifier of an SKS URI.
1309  *
1310  * @param uri the sks uri
1311  * @return NULL on error (not a valid SKS URI)
1312  */
1313 char *
1314 GNUNET_FS_uri_sks_get_content_id(const struct GNUNET_FS_Uri *uri)
1315 {
1316   if (!GNUNET_FS_uri_test_sks(uri))
1317     {
1318       GNUNET_break(0);
1319       return NULL;
1320     }
1321   return GNUNET_strdup(uri->data.sks.identifier);
1322 }
1323
1324
1325 /**
1326  * Is this a keyword URI?
1327  *
1328  * @param uri the uri
1329  * @return #GNUNET_YES if this is a KSK uri
1330  */
1331 int
1332 GNUNET_FS_uri_test_ksk(const struct GNUNET_FS_Uri *uri)
1333 {
1334 #if EXTRA_CHECKS
1335   unsigned int i;
1336
1337   if (uri->type == GNUNET_FS_URI_KSK)
1338     {
1339       for (i = 0; i < uri->data.ksk.keywordCount; i++)
1340         GNUNET_assert(uri->data.ksk.keywords[i] != NULL);
1341     }
1342 #endif
1343   return uri->type == GNUNET_FS_URI_KSK;
1344 }
1345
1346
1347 /**
1348  * Is this a file (or directory) URI?
1349  *
1350  * @param uri the uri to check
1351  * @return #GNUNET_YES if this is a CHK uri
1352  */
1353 int
1354 GNUNET_FS_uri_test_chk(const struct GNUNET_FS_Uri *uri)
1355 {
1356   return uri->type == GNUNET_FS_URI_CHK;
1357 }
1358
1359
1360 /**
1361  * What is the size of the file that this URI
1362  * refers to?
1363  *
1364  * @param uri the CHK URI to inspect
1365  * @return size of the file as specified in the CHK URI
1366  */
1367 uint64_t
1368 GNUNET_FS_uri_chk_get_file_size(const struct GNUNET_FS_Uri *uri)
1369 {
1370   switch (uri->type)
1371     {
1372     case GNUNET_FS_URI_CHK:
1373       return GNUNET_ntohll(uri->data.chk.file_length);
1374
1375     case GNUNET_FS_URI_LOC:
1376       return GNUNET_ntohll(uri->data.loc.fi.file_length);
1377
1378     default:
1379       GNUNET_assert(0);
1380     }
1381   return 0; /* unreachable */
1382 }
1383
1384
1385 /**
1386  * Is this a location URI?
1387  *
1388  * @param uri the uri to check
1389  * @return #GNUNET_YES if this is a LOC uri
1390  */
1391 int
1392 GNUNET_FS_uri_test_loc(const struct GNUNET_FS_Uri *uri)
1393 {
1394   return uri->type == GNUNET_FS_URI_LOC;
1395 }
1396
1397
1398 /**
1399  * Add a keyword as non-mandatory (with ' '-prefix) to the
1400  * given keyword list at offset 'index'.  The array is
1401  * guaranteed to be long enough.
1402  *
1403  * @param s keyword to add
1404  * @param array array to add the keyword to
1405  * @param index offset where to add the keyword
1406  */
1407 static void
1408 insert_non_mandatory_keyword(const char *s, char **array, int index)
1409 {
1410   char *nkword;
1411
1412   GNUNET_asprintf(&nkword,
1413                   " %s",  /* space to mark as 'non mandatory' */
1414                   s);
1415   array[index] = nkword;
1416 }
1417
1418
1419 /**
1420  * Test if the given keyword @a s is already present in the
1421  * given array, ignoring the '+'-mandatory prefix in the array.
1422  *
1423  * @param s keyword to test
1424  * @param array keywords to test against, with ' ' or '+' prefix to ignore
1425  * @param array_length length of the @a array
1426  * @return #GNUNET_YES if the keyword exists, #GNUNET_NO if not
1427  */
1428 static int
1429 find_duplicate(const char *s, const char **array, int array_length)
1430 {
1431   int j;
1432
1433   for (j = array_length - 1; j >= 0; j--)
1434     if (0 == strcmp(&array[j][1], s))
1435       return GNUNET_YES;
1436   return GNUNET_NO;
1437 }
1438
1439
1440 /**
1441  * FIXME: comment
1442  */
1443 static char *
1444 normalize_metadata(enum EXTRACTOR_MetaFormat format,
1445                    const char *data,
1446                    size_t data_len)
1447 {
1448   uint8_t *free_str = NULL;
1449   uint8_t *str_to_normalize = (uint8_t *)data;
1450   uint8_t *normalized;
1451   size_t r_len;
1452
1453   if (str_to_normalize == NULL)
1454     return NULL;
1455   /* Don't trust libextractor */
1456   if (format == EXTRACTOR_METAFORMAT_UTF8)
1457     {
1458       free_str = (uint8_t *)u8_check((const uint8_t *)data, data_len);
1459       if (free_str == NULL)
1460         free_str = NULL;
1461       else
1462         format = EXTRACTOR_METAFORMAT_C_STRING;
1463     }
1464   if (format == EXTRACTOR_METAFORMAT_C_STRING)
1465     {
1466       free_str = u8_strconv_from_encoding(data,
1467                                           locale_charset(),
1468                                           iconveh_escape_sequence);
1469       if (free_str == NULL)
1470         return NULL;
1471     }
1472
1473   normalized = u8_tolower(str_to_normalize,
1474                           strlen((char *)str_to_normalize),
1475                           NULL,
1476                           UNINORM_NFD,
1477                           NULL,
1478                           &r_len);
1479   /* free_str is allocated by libunistring internally, use free() */
1480   if (free_str != NULL)
1481     free(free_str);
1482   if (normalized != NULL)
1483     {
1484       /* u8_tolower allocates a non-NULL-terminated string! */
1485       free_str = GNUNET_malloc(r_len + 1);
1486       GNUNET_memcpy(free_str, normalized, r_len);
1487       free_str[r_len] = '\0';
1488       free(normalized);
1489       normalized = free_str;
1490     }
1491   return (char *)normalized;
1492 }
1493
1494
1495 /**
1496  * Counts the number of UTF-8 characters (not bytes) in the string,
1497  * returns that count.
1498  */
1499 static size_t
1500 u8_strcount(const uint8_t *s)
1501 {
1502   size_t count;
1503   ucs4_t c;
1504
1505   GNUNET_assert(s != NULL);
1506   if (s[0] == 0)
1507     return 0;
1508   for (count = 0; s != NULL; count++)
1509     s = u8_next(&c, s);
1510   return count - 1;
1511 }
1512
1513
1514 /**
1515  * Break the filename up by matching [], () and {} pairs to make
1516  * keywords. In case of nesting parentheses only the inner pair counts.
1517  * You can't escape parentheses to scan something like "[blah\{foo]" to
1518  * make a "blah{foo" keyword, this function is only a heuristic!
1519  *
1520  * @param s string to break down.
1521  * @param array array to fill with enclosed tokens. If NULL, then tokens
1522  *        are only counted.
1523  * @param index index at which to start filling the array (entries prior
1524  *        to it are used to check for duplicates). ignored if @a array == NULL.
1525  * @return number of tokens counted (including duplicates), or number of
1526  *         tokens extracted (excluding duplicates). 0 if there are no
1527  *         matching parens in the string (when counting), or when all tokens
1528  *         were duplicates (when extracting).
1529  */
1530 static int
1531 get_keywords_from_parens(const char *s, char **array, int index)
1532 {
1533   int count = 0;
1534   char *open_paren;
1535   char *close_paren;
1536   char *ss;
1537   char tmp;
1538
1539   if (NULL == s)
1540     return 0;
1541   ss = GNUNET_strdup(s);
1542   open_paren = ss - 1;
1543   while (NULL != (open_paren = strpbrk(open_paren + 1, "[{(")))
1544     {
1545       int match = 0;
1546
1547       close_paren = strpbrk(open_paren + 1, "]})");
1548       if (NULL == close_paren)
1549         continue;
1550       switch (open_paren[0])
1551         {
1552         case '[':
1553           if (']' == close_paren[0])
1554             match = 1;
1555           break;
1556
1557         case '{':
1558           if ('}' == close_paren[0])
1559             match = 1;
1560           break;
1561
1562         case '(':
1563           if (')' == close_paren[0])
1564             match = 1;
1565           break;
1566
1567         default:
1568           break;
1569         }
1570       if (match && (close_paren - open_paren > 1))
1571         {
1572           tmp = close_paren[0];
1573           close_paren[0] = '\0';
1574           /* Keywords must be at least 3 characters long */
1575           if (u8_strcount((const uint8_t *)&open_paren[1]) <= 2)
1576             {
1577               close_paren[0] = tmp;
1578               continue;
1579             }
1580           if (NULL != array)
1581             {
1582               char *normalized;
1583               if (GNUNET_NO == find_duplicate((const char *)&open_paren[1],
1584                                               (const char **)array,
1585                                               index + count))
1586                 {
1587                   insert_non_mandatory_keyword((const char *)&open_paren[1],
1588                                                array,
1589                                                index + count);
1590                   count++;
1591                 }
1592               normalized = normalize_metadata(EXTRACTOR_METAFORMAT_UTF8,
1593                                               &open_paren[1],
1594                                               close_paren - &open_paren[1]);
1595               if (normalized != NULL)
1596                 {
1597                   if (GNUNET_NO == find_duplicate((const char *)normalized,
1598                                                   (const char **)array,
1599                                                   index + count))
1600                     {
1601                       insert_non_mandatory_keyword((const char *)normalized,
1602                                                    array,
1603                                                    index + count);
1604                       count++;
1605                     }
1606                   GNUNET_free(normalized);
1607                 }
1608             }
1609           else
1610             count++;
1611           close_paren[0] = tmp;
1612         }
1613     }
1614   GNUNET_free(ss);
1615   return count;
1616 }
1617
1618
1619 /**
1620  * Where to break up keywords
1621  */
1622 #define TOKENS "_. /-!?#&+@\"\'\\;:,()[]{}$<>|"
1623
1624 /**
1625  * Break the filename up by TOKENS to make
1626  * keywords.
1627  *
1628  * @param s string to break down.
1629  * @param array array to fill with tokens. If NULL, then tokens are only
1630  *        counted.
1631  * @param index index at which to start filling the array (entries prior
1632  *        to it are used to check for duplicates). ignored if @a array == NULL.
1633  * @return number of tokens (>1) counted (including duplicates), or number of
1634  *         tokens extracted (excluding duplicates). 0 if there are no
1635  *         separators in the string (when counting), or when all tokens were
1636  *         duplicates (when extracting).
1637  */
1638 static int
1639 get_keywords_from_tokens(const char *s, char **array, int index)
1640 {
1641   char *p;
1642   char *ss;
1643   int seps = 0;
1644
1645   ss = GNUNET_strdup(s);
1646   for (p = strtok(ss, TOKENS); p != NULL; p = strtok(NULL, TOKENS))
1647     {
1648       /* Keywords must be at least 3 characters long */
1649       if (u8_strcount((const uint8_t *)p) <= 2)
1650         continue;
1651       if (NULL != array)
1652         {
1653           char *normalized;
1654           if (GNUNET_NO == find_duplicate(p, (const char **)array, index + seps))
1655             {
1656               insert_non_mandatory_keyword(p, array, index + seps);
1657               seps++;
1658             }
1659           normalized =
1660             normalize_metadata(EXTRACTOR_METAFORMAT_UTF8, p, strlen(p));
1661           if (normalized != NULL)
1662             {
1663               if (GNUNET_NO == find_duplicate((const char *)normalized,
1664                                               (const char **)array,
1665                                               index + seps))
1666                 {
1667                   insert_non_mandatory_keyword((const char *)normalized,
1668                                                array,
1669                                                index + seps);
1670                   seps++;
1671                 }
1672               GNUNET_free(normalized);
1673             }
1674         }
1675       else
1676         seps++;
1677     }
1678   GNUNET_free(ss);
1679   return seps;
1680 }
1681 #undef TOKENS
1682
1683
1684 /**
1685  * Function called on each value in the meta data.
1686  * Adds it to the URI.
1687  *
1688  * @param cls URI to update
1689  * @param plugin_name name of the plugin that produced this value;
1690  *        special values can be used (i.e. '&lt;zlib&gt;' for zlib being
1691  *        used in the main libextractor library and yielding
1692  *        meta data).
1693  * @param type libextractor-type describing the meta data
1694  * @param format basic format information about data
1695  * @param data_mime_type mime-type of data (not of the original file);
1696  *        can be NULL (if mime-type is not known)
1697  * @param data actual meta-data found
1698  * @param data_len number of bytes in @a data
1699  * @return 0 (always)
1700  */
1701 static int
1702 gather_uri_data(void *cls,
1703                 const char *plugin_name,
1704                 enum EXTRACTOR_MetaType type,
1705                 enum EXTRACTOR_MetaFormat format,
1706                 const char *data_mime_type,
1707                 const char *data,
1708                 size_t data_len)
1709 {
1710   struct GNUNET_FS_Uri *uri = cls;
1711   char *normalized_data;
1712   const char *sep;
1713
1714   if ((format != EXTRACTOR_METAFORMAT_UTF8) &&
1715       (format != EXTRACTOR_METAFORMAT_C_STRING))
1716     return 0;
1717   /* Keywords must be at least 3 characters long
1718    * If given non-utf8 string it will, most likely, find it to be invalid,
1719    * and will return the length of its valid part, skipping the keyword.
1720    * If it does - fix the extractor, not this check!
1721    */
1722   if (u8_strcount((const uint8_t *)data) <= 2)
1723     return 0;
1724   if ((EXTRACTOR_METATYPE_MIMETYPE == type) &&
1725       (NULL != (sep = memchr(data, '/', data_len))) && (sep != data))
1726     {
1727       char *xtra;
1728
1729       GNUNET_asprintf(&xtra, "mimetype:%.*s", (int)(sep - data), data);
1730       if (!find_duplicate(xtra,
1731                           (const char **)uri->data.ksk.keywords,
1732                           uri->data.ksk.keywordCount))
1733         {
1734           insert_non_mandatory_keyword(xtra,
1735                                        uri->data.ksk.keywords,
1736                                        uri->data.ksk.keywordCount);
1737           uri->data.ksk.keywordCount++;
1738         }
1739       GNUNET_free(xtra);
1740     }
1741
1742   normalized_data = normalize_metadata(format, data, data_len);
1743   if (!find_duplicate(data,
1744                       (const char **)uri->data.ksk.keywords,
1745                       uri->data.ksk.keywordCount))
1746     {
1747       insert_non_mandatory_keyword(data,
1748                                    uri->data.ksk.keywords,
1749                                    uri->data.ksk.keywordCount);
1750       uri->data.ksk.keywordCount++;
1751     }
1752   if (NULL != normalized_data)
1753     {
1754       if (!find_duplicate(normalized_data,
1755                           (const char **)uri->data.ksk.keywords,
1756                           uri->data.ksk.keywordCount))
1757         {
1758           insert_non_mandatory_keyword(normalized_data,
1759                                        uri->data.ksk.keywords,
1760                                        uri->data.ksk.keywordCount);
1761           uri->data.ksk.keywordCount++;
1762         }
1763       GNUNET_free(normalized_data);
1764     }
1765   return 0;
1766 }
1767
1768
1769 /**
1770  * Construct a keyword-URI from meta-data (take all entries
1771  * in the meta-data and construct one large keyword URI
1772  * that lists all keywords that can be found in the meta-data).
1773  *
1774  * @param md metadata to use
1775  * @return NULL on error, otherwise a KSK URI
1776  */
1777 struct GNUNET_FS_Uri *
1778 GNUNET_FS_uri_ksk_create_from_meta_data(
1779   const struct GNUNET_CONTAINER_MetaData *md)
1780 {
1781   struct GNUNET_FS_Uri *ret;
1782   char *filename;
1783   char *full_name = NULL;
1784   char *ss;
1785   int ent;
1786   int tok_keywords = 0;
1787   int paren_keywords = 0;
1788
1789   if (NULL == md)
1790     return NULL;
1791   ret = GNUNET_new(struct GNUNET_FS_Uri);
1792   ret->type = GNUNET_FS_URI_KSK;
1793   ent = GNUNET_CONTAINER_meta_data_iterate(md, NULL, NULL);
1794   if (ent > 0)
1795     {
1796       full_name = GNUNET_CONTAINER_meta_data_get_first_by_types(
1797         md,
1798         EXTRACTOR_METATYPE_GNUNET_ORIGINAL_FILENAME,
1799         -1);
1800       if (NULL != full_name)
1801         {
1802           filename = full_name;
1803           while (NULL != (ss = strstr(filename, DIR_SEPARATOR_STR)))
1804             filename = ss + 1;
1805           tok_keywords = get_keywords_from_tokens(filename, NULL, 0);
1806           paren_keywords = get_keywords_from_parens(filename, NULL, 0);
1807         }
1808       /* x3 because there might be a normalized variant of every keyword,
1809          plus theoretically one more for mime... */
1810       ret->data.ksk.keywords =
1811         GNUNET_new_array((ent + tok_keywords + paren_keywords) * 3, char *);
1812       GNUNET_CONTAINER_meta_data_iterate(md, &gather_uri_data, ret);
1813     }
1814   if (tok_keywords > 0)
1815     ret->data.ksk.keywordCount +=
1816       get_keywords_from_tokens(filename,
1817                                ret->data.ksk.keywords,
1818                                ret->data.ksk.keywordCount);
1819   if (paren_keywords > 0)
1820     ret->data.ksk.keywordCount +=
1821       get_keywords_from_parens(filename,
1822                                ret->data.ksk.keywords,
1823                                ret->data.ksk.keywordCount);
1824   if (ent > 0)
1825     GNUNET_free_non_null(full_name);
1826   return ret;
1827 }
1828
1829
1830 /**
1831  * In URI-encoding, does the given character
1832  * need to be encoded using %-encoding?
1833  */
1834 static int
1835 needs_percent(char c)
1836 {
1837   return(!((isalnum((unsigned char)c)) || (c == '-') || (c == '_') ||
1838            (c == '.') || (c == '~')));
1839 }
1840
1841
1842 /**
1843  * Convert a KSK URI to a string.
1844  *
1845  * @param uri the URI to convert
1846  * @return NULL on error (i.e. keywordCount == 0)
1847  */
1848 static char *
1849 uri_ksk_to_string(const struct GNUNET_FS_Uri *uri)
1850 {
1851   char **keywords;
1852   unsigned int keywordCount;
1853   size_t n;
1854   char *ret;
1855   unsigned int i;
1856   unsigned int j;
1857   unsigned int wpos;
1858   size_t slen;
1859   const char *keyword;
1860
1861   if (uri->type != GNUNET_FS_URI_KSK)
1862     return NULL;
1863   keywords = uri->data.ksk.keywords;
1864   keywordCount = uri->data.ksk.keywordCount;
1865   n = keywordCount + strlen(GNUNET_FS_URI_PREFIX) +
1866       strlen(GNUNET_FS_URI_KSK_INFIX) + 1;
1867   for (i = 0; i < keywordCount; i++)
1868     {
1869       keyword = keywords[i];
1870       slen = strlen(keyword);
1871       n += slen;
1872       for (j = 0; j < slen; j++)
1873         {
1874           if ((j == 0) && (keyword[j] == ' '))
1875             {
1876               n--;
1877               continue; /* skip leading space */
1878             }
1879           if (needs_percent(keyword[j]))
1880             n += 2; /* will use %-encoding */
1881         }
1882     }
1883   ret = GNUNET_malloc(n);
1884   strcpy(ret, GNUNET_FS_URI_PREFIX);
1885   strcat(ret, GNUNET_FS_URI_KSK_INFIX);
1886   wpos = strlen(ret);
1887   for (i = 0; i < keywordCount; i++)
1888     {
1889       keyword = keywords[i];
1890       slen = strlen(keyword);
1891       for (j = 0; j < slen; j++)
1892         {
1893           if ((j == 0) && (keyword[j] == ' '))
1894             continue; /* skip leading space */
1895           if (needs_percent(keyword[j]))
1896             {
1897               sprintf(&ret[wpos], "%%%02X", (unsigned char)keyword[j]);
1898               wpos += 3;
1899             }
1900           else
1901             {
1902               ret[wpos++] = keyword[j];
1903             }
1904         }
1905       if (i != keywordCount - 1)
1906         ret[wpos++] = '+';
1907     }
1908   return ret;
1909 }
1910
1911
1912 /**
1913  * Convert SKS URI to a string.
1914  *
1915  * @param uri sks uri to convert
1916  * @return NULL on error
1917  */
1918 static char *
1919 uri_sks_to_string(const struct GNUNET_FS_Uri *uri)
1920 {
1921   char *ret;
1922   char buf[1024];
1923
1924   if (GNUNET_FS_URI_SKS != uri->type)
1925     return NULL;
1926   ret =
1927     GNUNET_STRINGS_data_to_string(&uri->data.sks.ns,
1928                                   sizeof(struct GNUNET_CRYPTO_EcdsaPublicKey),
1929                                   buf,
1930                                   sizeof(buf));
1931   GNUNET_assert(NULL != ret);
1932   ret[0] = '\0';
1933   GNUNET_asprintf(&ret,
1934                   "%s%s%s/%s",
1935                   GNUNET_FS_URI_PREFIX,
1936                   GNUNET_FS_URI_SKS_INFIX,
1937                   buf,
1938                   uri->data.sks.identifier);
1939   return ret;
1940 }
1941
1942
1943 /**
1944  * Convert a CHK URI to a string.
1945  *
1946  * @param uri chk uri to convert
1947  * @return NULL on error
1948  */
1949 static char *
1950 uri_chk_to_string(const struct GNUNET_FS_Uri *uri)
1951 {
1952   const struct FileIdentifier *fi;
1953   char *ret;
1954   struct GNUNET_CRYPTO_HashAsciiEncoded keyhash;
1955   struct GNUNET_CRYPTO_HashAsciiEncoded queryhash;
1956
1957   if (uri->type != GNUNET_FS_URI_CHK)
1958     return NULL;
1959   fi = &uri->data.chk;
1960   GNUNET_CRYPTO_hash_to_enc(&fi->chk.key, &keyhash);
1961   GNUNET_CRYPTO_hash_to_enc(&fi->chk.query, &queryhash);
1962
1963   GNUNET_asprintf(&ret,
1964                   "%s%s%s.%s.%llu",
1965                   GNUNET_FS_URI_PREFIX,
1966                   GNUNET_FS_URI_CHK_INFIX,
1967                   (const char *)&keyhash,
1968                   (const char *)&queryhash,
1969                   GNUNET_ntohll(fi->file_length));
1970   return ret;
1971 }
1972
1973
1974 /**
1975  * Convert a LOC URI to a string.
1976  *
1977  * @param uri loc uri to convert
1978  * @return NULL on error
1979  */
1980 static char *
1981 uri_loc_to_string(const struct GNUNET_FS_Uri *uri)
1982 {
1983   char *ret;
1984   struct GNUNET_CRYPTO_HashAsciiEncoded keyhash;
1985   struct GNUNET_CRYPTO_HashAsciiEncoded queryhash;
1986   char *peer_id;
1987   char peer_sig[SIGNATURE_ASCII_LENGTH + 1];
1988
1989   GNUNET_CRYPTO_hash_to_enc(&uri->data.loc.fi.chk.key, &keyhash);
1990   GNUNET_CRYPTO_hash_to_enc(&uri->data.loc.fi.chk.query, &queryhash);
1991   peer_id =
1992     GNUNET_CRYPTO_eddsa_public_key_to_string(&uri->data.loc.peer.public_key);
1993   GNUNET_assert(
1994     NULL !=
1995     GNUNET_STRINGS_data_to_string(&uri->data.loc.contentSignature,
1996                                   sizeof(struct GNUNET_CRYPTO_EddsaSignature),
1997                                   peer_sig,
1998                                   sizeof(peer_sig)));
1999   GNUNET_asprintf(&ret,
2000                   "%s%s%s.%s.%llu.%s.%s.%llu",
2001                   GNUNET_FS_URI_PREFIX,
2002                   GNUNET_FS_URI_LOC_INFIX,
2003                   (const char *)&keyhash,
2004                   (const char *)&queryhash,
2005                   (unsigned long long)GNUNET_ntohll(
2006                     uri->data.loc.fi.file_length),
2007                   peer_id,
2008                   peer_sig,
2009                   (unsigned long long)
2010                   uri->data.loc.expirationTime.abs_value_us /
2011                   1000000LL);
2012   GNUNET_free(peer_id);
2013   return ret;
2014 }
2015
2016
2017 /**
2018  * Convert a URI to a UTF-8 String.
2019  *
2020  * @param uri uri to convert to a string
2021  * @return the UTF-8 string
2022  */
2023 char *
2024 GNUNET_FS_uri_to_string(const struct GNUNET_FS_Uri *uri)
2025 {
2026   if (uri == NULL)
2027     {
2028       GNUNET_break(0);
2029       return NULL;
2030     }
2031   switch (uri->type)
2032     {
2033     case GNUNET_FS_URI_KSK:
2034       return uri_ksk_to_string(uri);
2035
2036     case GNUNET_FS_URI_SKS:
2037       return uri_sks_to_string(uri);
2038
2039     case GNUNET_FS_URI_CHK:
2040       return uri_chk_to_string(uri);
2041
2042     case GNUNET_FS_URI_LOC:
2043       return uri_loc_to_string(uri);
2044
2045     default:
2046       GNUNET_break(0);
2047       return NULL;
2048     }
2049 }
2050
2051 /* end of fs_uri.c */