1 /* XML resource locating rules
2 Copyright (C) 2015, 2019-2020 Free Software Foundation, Inc.
3
4 This file was written by Daiki Ueno <ueno@gnu.org>, 2015.
5
6 This program is free software: you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 3 of the License, or
9 (at your option) any later version.
10
11 This program is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
15
16 You should have received a copy of the GNU General Public License
17 along with this program. If not, see <https://www.gnu.org/licenses/>. */
18
19 #ifdef HAVE_CONFIG_H
20 # include <config.h>
21 #endif
22
23 /* Specification. */
24 #include "locating-rule.h"
25
26 #include "basename-lgpl.h"
27 #include "concat-filename.h"
28 #include "c-strcase.h"
29
30 #if HAVE_DIRENT_H
31 # include <dirent.h>
32 #endif
33
34 #if HAVE_DIRENT_H
35 # define HAVE_DIR 1
36 #else
37 # define HAVE_DIR 0
38 #endif
39
40 #include "dir-list.h"
41 #include <errno.h>
42 #include "error.h"
43 #include "filename.h"
44 #include <fnmatch.h>
45 #include "gettext.h"
46 #include "mem-hash-map.h"
47 #include <libxml/parser.h>
48 #include <libxml/uri.h>
49 #include "xalloc.h"
50
51 #define _(str) gettext (str)
52
53 #define LOCATING_RULES_NS "https://www.gnu.org/s/gettext/ns/locating-rules/1.0"
54
55 struct document_locating_rule_ty
56 {
57 char *ns;
58 char *local_name;
59
60 char *target;
61 };
62
63 struct document_locating_rule_list_ty
64 {
65 struct document_locating_rule_ty *items;
66 size_t nitems;
67 size_t nitems_max;
68 };
69
70 struct locating_rule_ty
71 {
72 char *pattern;
73 char *name;
74
75 struct document_locating_rule_list_ty doc_rules;
76 char *target;
77 };
78
79 struct locating_rule_list_ty
80 {
81 struct locating_rule_ty *items;
82 size_t nitems;
83 size_t nitems_max;
84 };
85
86 static char *
get_attribute(xmlNode * node,const char * attr)87 get_attribute (xmlNode *node, const char *attr)
88 {
89 xmlChar *value;
90 char *result;
91
92 value = xmlGetProp (node, BAD_CAST attr);
93 result = xstrdup ((const char *) value);
94 xmlFree (value);
95
96 return result;
97 }
98
99 static const char *
document_locating_rule_match(struct document_locating_rule_ty * rule,xmlDoc * doc)100 document_locating_rule_match (struct document_locating_rule_ty *rule,
101 xmlDoc *doc)
102 {
103 xmlNode *root;
104
105 root = xmlDocGetRootElement (doc);
106 if (rule->ns != NULL)
107 {
108 if (root->ns == NULL
109 || !xmlStrEqual (root->ns->href, BAD_CAST rule->ns))
110 return NULL;
111 }
112
113 if (rule->local_name != NULL)
114 {
115 if (!xmlStrEqual (root->name,
116 BAD_CAST rule->local_name))
117 return NULL;
118 }
119
120 return rule->target;
121 }
122
123 static const char *
locating_rule_match(struct locating_rule_ty * rule,const char * filename,const char * name)124 locating_rule_match (struct locating_rule_ty *rule,
125 const char *filename,
126 const char *name)
127 {
128 if (name != NULL)
129 {
130 if (rule->name == NULL || c_strcasecmp (name, rule->name) != 0)
131 return NULL;
132 }
133 else
134 {
135 const char *base;
136 char *reduced;
137 int err;
138
139 base = strrchr (filename, '/');
140 if (!base)
141 base = filename;
142
143 reduced = xstrdup (base);
144 /* Remove a trailing ".in" - it's a generic suffix. */
145 while (strlen (reduced) >= 3
146 && memcmp (reduced + strlen (reduced) - 3, ".in", 3) == 0)
147 reduced[strlen (reduced) - 3] = '\0';
148
149 err = fnmatch (rule->pattern, last_component (reduced), FNM_PATHNAME);
150 free (reduced);
151 if (err != 0)
152 return NULL;
153 }
154
155 /* Check documentRules. */
156 if (rule->doc_rules.nitems > 0)
157 {
158 const char *target;
159 xmlDoc *doc;
160 size_t i;
161
162 doc = xmlReadFile (filename, NULL,
163 XML_PARSE_NONET
164 | XML_PARSE_NOWARNING
165 | XML_PARSE_NOBLANKS
166 | XML_PARSE_NOERROR);
167 if (doc == NULL)
168 {
169 xmlError *err = xmlGetLastError ();
170 error (0, 0, _("cannot read %s: %s"), filename, err->message);
171 return NULL;
172 }
173
174 for (i = 0, target = NULL; i < rule->doc_rules.nitems; i++)
175 {
176 target =
177 document_locating_rule_match (&rule->doc_rules.items[i], doc);
178 if (target)
179 break;
180 }
181 xmlFreeDoc (doc);
182 if (target != NULL)
183 return target;
184 }
185
186 if (rule->target != NULL)
187 return rule->target;
188
189 return NULL;
190 }
191
192 const char *
locating_rule_list_locate(struct locating_rule_list_ty * rules,const char * filename,const char * name)193 locating_rule_list_locate (struct locating_rule_list_ty *rules,
194 const char *filename,
195 const char *name)
196 {
197 size_t i;
198
199 for (i = 0; i < rules->nitems; i++)
200 {
201 if (IS_RELATIVE_FILE_NAME (filename))
202 {
203 int j;
204
205 for (j = 0; ; ++j)
206 {
207 const char *dir = dir_list_nth (j);
208 char *new_filename;
209 const char *target;
210
211 if (dir == NULL)
212 break;
213
214 new_filename = xconcatenated_filename (dir, filename, NULL);
215 target = locating_rule_match (&rules->items[i], new_filename,
216 name);
217 free (new_filename);
218 if (target != NULL)
219 return target;
220 }
221 }
222 else
223 {
224 const char *target =
225 locating_rule_match (&rules->items[i], filename, name);
226
227 if (target != NULL)
228 return target;
229 }
230 }
231
232 return NULL;
233 }
234
235 static void
missing_attribute(xmlNode * node,const char * attribute)236 missing_attribute (xmlNode *node, const char *attribute)
237 {
238 error (0, 0, _("\"%s\" node does not have \"%s\""), node->name, attribute);
239 }
240
241 static void
document_locating_rule_destroy(struct document_locating_rule_ty * rule)242 document_locating_rule_destroy (struct document_locating_rule_ty *rule)
243 {
244 free (rule->ns);
245 free (rule->local_name);
246 free (rule->target);
247 }
248
249 static void
document_locating_rule_list_add(struct document_locating_rule_list_ty * rules,xmlNode * node)250 document_locating_rule_list_add (struct document_locating_rule_list_ty *rules,
251 xmlNode *node)
252 {
253 struct document_locating_rule_ty rule;
254
255 if (!xmlHasProp (node, BAD_CAST "target"))
256 {
257 missing_attribute (node, "target");
258 return;
259 }
260
261 memset (&rule, 0, sizeof (struct document_locating_rule_ty));
262
263 if (xmlHasProp (node, BAD_CAST "ns"))
264 rule.ns = get_attribute (node, "ns");
265 if (xmlHasProp (node, BAD_CAST "localName"))
266 rule.local_name = get_attribute (node, "localName");
267 rule.target = get_attribute (node, "target");
268
269 if (rules->nitems == rules->nitems_max)
270 {
271 rules->nitems_max = 2 * rules->nitems_max + 1;
272 rules->items =
273 xrealloc (rules->items,
274 sizeof (struct document_locating_rule_ty)
275 * rules->nitems_max);
276 }
277 memcpy (&rules->items[rules->nitems++], &rule,
278 sizeof (struct document_locating_rule_ty));
279 }
280
281 static void
locating_rule_destroy(struct locating_rule_ty * rule)282 locating_rule_destroy (struct locating_rule_ty *rule)
283 {
284 size_t i;
285
286 for (i = 0; i < rule->doc_rules.nitems; i++)
287 document_locating_rule_destroy (&rule->doc_rules.items[i]);
288 free (rule->doc_rules.items);
289
290 free (rule->name);
291 free (rule->pattern);
292 free (rule->target);
293 }
294
295 static bool
locating_rule_list_add_from_file(struct locating_rule_list_ty * rules,const char * rule_file_name)296 locating_rule_list_add_from_file (struct locating_rule_list_ty *rules,
297 const char *rule_file_name)
298 {
299 xmlDoc *doc;
300 xmlNode *root, *node;
301
302 doc = xmlReadFile (rule_file_name, "utf-8",
303 XML_PARSE_NONET
304 | XML_PARSE_NOWARNING
305 | XML_PARSE_NOBLANKS
306 | XML_PARSE_NOERROR);
307 if (doc == NULL)
308 {
309 error (0, 0, _("cannot read XML file %s"), rule_file_name);
310 return false;
311 }
312
313 root = xmlDocGetRootElement (doc);
314 if (!(xmlStrEqual (root->name, BAD_CAST "locatingRules")
315 #if 0
316 && root->ns
317 && xmlStrEqual (root->ns->href, BAD_CAST LOCATING_RULES_NS)
318 #endif
319 ))
320 {
321 error (0, 0, _("the root element is not \"locatingRules\""));
322 xmlFreeDoc (doc);
323 return false;
324 }
325
326 for (node = root->children; node; node = node->next)
327 {
328 if (xmlStrEqual (node->name, BAD_CAST "locatingRule"))
329 {
330 struct locating_rule_ty rule;
331
332 if (!xmlHasProp (node, BAD_CAST "pattern"))
333 {
334 missing_attribute (node, "pattern");
335 xmlFreeDoc (doc);
336 }
337 else
338 {
339 memset (&rule, 0, sizeof (struct locating_rule_ty));
340 rule.pattern = get_attribute (node, "pattern");
341 if (xmlHasProp (node, BAD_CAST "name"))
342 rule.name = get_attribute (node, "name");
343 if (xmlHasProp (node, BAD_CAST "target"))
344 rule.target = get_attribute (node, "target");
345 else
346 {
347 xmlNode *n;
348
349 for (n = node->children; n; n = n->next)
350 {
351 if (xmlStrEqual (n->name, BAD_CAST "documentRule"))
352 document_locating_rule_list_add (&rule.doc_rules, n);
353 }
354 }
355 if (rules->nitems == rules->nitems_max)
356 {
357 rules->nitems_max = 2 * rules->nitems_max + 1;
358 rules->items =
359 xrealloc (rules->items,
360 sizeof (struct locating_rule_ty) * rules->nitems_max);
361 }
362 memcpy (&rules->items[rules->nitems++], &rule,
363 sizeof (struct locating_rule_ty));
364 }
365 }
366 }
367
368 xmlFreeDoc (doc);
369 return true;
370 }
371
372 bool
locating_rule_list_add_from_directory(struct locating_rule_list_ty * rules,const char * directory)373 locating_rule_list_add_from_directory (struct locating_rule_list_ty *rules,
374 const char *directory)
375 {
376 #if HAVE_DIR
377 DIR *dirp;
378
379 dirp = opendir (directory);
380 if (dirp == NULL)
381 return false;
382
383 for (;;)
384 {
385 struct dirent *dp;
386
387 errno = 0;
388 dp = readdir (dirp);
389 if (dp != NULL)
390 {
391 const char *name = dp->d_name;
392 size_t namlen = strlen (name);
393
394 if (namlen > 4 && memcmp (name + namlen - 4, ".loc", 4) == 0)
395 {
396 char *locator_file_name =
397 xconcatenated_filename (directory, name, NULL);
398 locating_rule_list_add_from_file (rules, locator_file_name);
399 free (locator_file_name);
400 }
401 }
402 else if (errno != 0)
403 return false;
404 else
405 break;
406 }
407 if (closedir (dirp))
408 return false;
409
410 #endif
411 return true;
412 }
413
414 struct locating_rule_list_ty *
locating_rule_list_alloc(void)415 locating_rule_list_alloc (void)
416 {
417 struct locating_rule_list_ty *result;
418
419 xmlCheckVersion (LIBXML_VERSION);
420
421 result = XCALLOC (1, struct locating_rule_list_ty);
422
423 return result;
424 }
425
426 void
locating_rule_list_destroy(struct locating_rule_list_ty * rules)427 locating_rule_list_destroy (struct locating_rule_list_ty *rules)
428 {
429 while (rules->nitems-- > 0)
430 locating_rule_destroy (&rules->items[rules->nitems]);
431 free (rules->items);
432 }
433
434 void
locating_rule_list_free(struct locating_rule_list_ty * rules)435 locating_rule_list_free (struct locating_rule_list_ty *rules)
436 {
437 if (rules != NULL)
438 locating_rule_list_destroy (rules);
439 free (rules);
440 }
441