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