(root)/
gettext-0.22.4/
gettext-tools/
src/
read-desktop.c
       1  /* Reading Desktop Entry files.
       2     Copyright (C) 1995-1998, 2000-2003, 2005-2006, 2008-2009, 2014-2019, 2023 Free Software Foundation, Inc.
       3     This file was written by Daiki Ueno <ueno@gnu.org>.
       4  
       5     This program is free software: you can redistribute it and/or modify
       6     it under the terms of the GNU General Public License as published by
       7     the Free Software Foundation; either version 3 of the License, or
       8     (at your option) any later version.
       9  
      10     This program is distributed in the hope that it will be useful,
      11     but WITHOUT ANY WARRANTY; without even the implied warranty of
      12     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      13     GNU General Public License for more details.
      14  
      15     You should have received a copy of the GNU General Public License
      16     along with this program.  If not, see <https://www.gnu.org/licenses/>.  */
      17  
      18  #ifdef HAVE_CONFIG_H
      19  # include <config.h>
      20  #endif
      21  
      22  /* Specification.  */
      23  #include "read-desktop.h"
      24  
      25  #include "xalloc.h"
      26  
      27  #include <assert.h>
      28  #include <errno.h>
      29  #include <stdbool.h>
      30  #include <stdio.h>
      31  #include <stdlib.h>
      32  #include <string.h>
      33  
      34  #include "error.h"
      35  #include "error-progname.h"
      36  #include "xalloc.h"
      37  #include "xvasprintf.h"
      38  #include "c-ctype.h"
      39  #include "po-lex.h"
      40  #include "po-xerror.h"
      41  #include "gettext.h"
      42  
      43  #define _(str) gettext (str)
      44  
      45  /* The syntax of a Desktop Entry file is defined at
      46     https://standards.freedesktop.org/desktop-entry-spec/latest/index.html.  */
      47  
      48  desktop_reader_ty *
      49  desktop_reader_alloc (desktop_reader_class_ty *method_table)
      50  {
      51    desktop_reader_ty *reader;
      52  
      53    reader = (desktop_reader_ty *) xmalloc (method_table->size);
      54    reader->methods = method_table;
      55    if (method_table->constructor)
      56      method_table->constructor (reader);
      57    return reader;
      58  }
      59  
      60  void
      61  desktop_reader_free (desktop_reader_ty *reader)
      62  {
      63    if (reader->methods->destructor)
      64      reader->methods->destructor (reader);
      65    free (reader);
      66  }
      67  
      68  void
      69  desktop_reader_handle_group (desktop_reader_ty *reader, const char *group)
      70  {
      71    if (reader->methods->handle_group)
      72      reader->methods->handle_group (reader, group);
      73  }
      74  
      75  void
      76  desktop_reader_handle_pair (desktop_reader_ty *reader,
      77                              lex_pos_ty *key_pos,
      78                              const char *key,
      79                              const char *locale,
      80                              const char *value)
      81  {
      82    if (reader->methods->handle_pair)
      83      reader->methods->handle_pair (reader, key_pos, key, locale, value);
      84  }
      85  
      86  void
      87  desktop_reader_handle_comment (desktop_reader_ty *reader, const char *s)
      88  {
      89    if (reader->methods->handle_comment)
      90      reader->methods->handle_comment (reader, s);
      91  }
      92  
      93  void
      94  desktop_reader_handle_blank (desktop_reader_ty *reader, const char *s)
      95  {
      96    if (reader->methods->handle_blank)
      97      reader->methods->handle_blank (reader, s);
      98  }
      99  
     100  /* Real filename, used in error messages about the input file.  */
     101  static const char *real_file_name;
     102  
     103  /* File name and line number.  */
     104  extern lex_pos_ty gram_pos;
     105  
     106  /* The input file stream.  */
     107  static FILE *fp;
     108  
     109  
     110  static int
     111  phase1_getc ()
     112  {
     113    int c;
     114  
     115    c = getc (fp);
     116  
     117    if (c == EOF)
     118      {
     119        if (ferror (fp))
     120          {
     121            const char *errno_description = strerror (errno);
     122            po_xerror (PO_SEVERITY_FATAL_ERROR, NULL, NULL, 0, 0, false,
     123                       xasprintf ("%s: %s",
     124                                  xasprintf (_("error while reading \"%s\""),
     125                                             real_file_name),
     126                                  errno_description));
     127          }
     128        return EOF;
     129      }
     130  
     131    return c;
     132  }
     133  
     134  static inline void
     135  phase1_ungetc (int c)
     136  {
     137    if (c != EOF)
     138      ungetc (c, fp);
     139  }
     140  
     141  
     142  static unsigned char phase2_pushback[2];
     143  static int phase2_pushback_length;
     144  
     145  static int
     146  phase2_getc ()
     147  {
     148    int c;
     149  
     150    if (phase2_pushback_length)
     151      c = phase2_pushback[--phase2_pushback_length];
     152    else
     153      {
     154        c = phase1_getc ();
     155  
     156        if (c == '\r')
     157          {
     158            int c2 = phase1_getc ();
     159            if (c2 == '\n')
     160              c = c2;
     161            else
     162              phase1_ungetc (c2);
     163          }
     164      }
     165  
     166    if (c == '\n')
     167      gram_pos.line_number++;
     168  
     169    return c;
     170  }
     171  
     172  static void
     173  phase2_ungetc (int c)
     174  {
     175    if (c == '\n')
     176      --gram_pos.line_number;
     177    if (c != EOF)
     178      phase2_pushback[phase2_pushback_length++] = c;
     179  }
     180  
     181  enum token_type_ty
     182  {
     183    token_type_eof,
     184    token_type_group,
     185    token_type_pair,
     186    /* Unlike other scanners, preserve comments and blank lines for
     187       merging translations back into a desktop file, with msgfmt.  */
     188    token_type_comment,
     189    token_type_blank,
     190    token_type_other
     191  };
     192  typedef enum token_type_ty token_type_ty;
     193  
     194  typedef struct token_ty token_ty;
     195  struct token_ty
     196  {
     197    token_type_ty type;
     198    char *string;
     199    const char *value;
     200    const char *locale;
     201  };
     202  
     203  /* Free the memory pointed to by a 'struct token_ty'.  */
     204  static inline void
     205  free_token (token_ty *tp)
     206  {
     207    if (tp->type == token_type_group || tp->type == token_type_pair
     208        || tp->type == token_type_comment || tp->type == token_type_blank)
     209      free (tp->string);
     210  }
     211  
     212  static void
     213  desktop_lex (token_ty *tp)
     214  {
     215    static char *buffer;
     216    static size_t bufmax;
     217    size_t bufpos;
     218  
     219  #undef APPEND
     220  #define APPEND(c)                               \
     221    do                                            \
     222      {                                           \
     223        if (bufpos >= bufmax)                     \
     224          {                                       \
     225            bufmax += 100;                        \
     226            buffer = xrealloc (buffer, bufmax);   \
     227          }                                       \
     228        buffer[bufpos++] = c;                     \
     229      }                                           \
     230    while (0)
     231  
     232    bufpos = 0;
     233    for (;;)
     234      {
     235        int c;
     236  
     237        c = phase2_getc ();
     238  
     239        switch (c)
     240          {
     241          case EOF:
     242            tp->type = token_type_eof;
     243            return;
     244  
     245          case '[':
     246            {
     247              bool non_blank = false;
     248  
     249              for (;;)
     250                {
     251                  c = phase2_getc ();
     252                  if (c == EOF || c == ']')
     253                    break;
     254                  if (c == '\n')
     255                    {
     256                      po_xerror (PO_SEVERITY_WARNING, NULL,
     257                                 real_file_name, gram_pos.line_number, 0, false,
     258                                 _("unterminated group name"));
     259                      break;
     260                    }
     261                  /* Group names may contain all ASCII characters
     262                     except for '[' and ']' and control characters.  */
     263                  if (!(c_isascii (c) && c != '[' && !c_iscntrl (c)))
     264                    break;
     265                  APPEND (c);
     266                }
     267              /* Skip until newline.  */
     268              while (c != '\n' && c != EOF)
     269                {
     270                  c = phase2_getc ();
     271                  if (c == EOF)
     272                    break;
     273                  if (!c_isspace (c))
     274                    non_blank = true;
     275                }
     276              if (non_blank)
     277                po_xerror (PO_SEVERITY_WARNING, NULL,
     278                           real_file_name, gram_pos.line_number, 0, false,
     279                           _("invalid non-blank character"));
     280              APPEND (0);
     281              tp->type = token_type_group;
     282              tp->string = xstrdup (buffer);
     283              return;
     284            }
     285  
     286          case '#':
     287            {
     288              /* Read until newline.  */
     289              for (;;)
     290                {
     291                  c = phase2_getc ();
     292                  if (c == EOF || c == '\n')
     293                    break;
     294                  APPEND (c);
     295                }
     296              APPEND (0);
     297              tp->type = token_type_comment;
     298              tp->string = xstrdup (buffer);
     299              return;
     300            }
     301  
     302          case 'A': case 'B': case 'C': case 'D': case 'E': case 'F':
     303          case 'G': case 'H': case 'I': case 'J': case 'K': case 'L':
     304          case 'M': case 'N': case 'O': case 'P': case 'Q': case 'R':
     305          case 'S': case 'T': case 'U': case 'V': case 'W': case 'X':
     306          case 'Y': case 'Z':
     307          case '-':
     308          case 'a': case 'b': case 'c': case 'd': case 'e': case 'f':
     309          case 'g': case 'h': case 'i': case 'j': case 'k': case 'l':
     310          case 'm': case 'n': case 'o': case 'p': case 'q': case 'r':
     311          case 's': case 't': case 'u': case 'v': case 'w': case 'x':
     312          case 'y': case 'z':
     313          case '0': case '1': case '2': case '3': case '4':
     314          case '5': case '6': case '7': case '8': case '9':
     315            {
     316              size_t locale_start;
     317              bool found_locale = false;
     318              size_t value_start;
     319              for (;;)
     320                {
     321                  APPEND (c);
     322  
     323                  c = phase2_getc ();
     324                  switch (c)
     325                    {
     326                    case 'A': case 'B': case 'C': case 'D': case 'E': case 'F':
     327                    case 'G': case 'H': case 'I': case 'J': case 'K': case 'L':
     328                    case 'M': case 'N': case 'O': case 'P': case 'Q': case 'R':
     329                    case 'S': case 'T': case 'U': case 'V': case 'W': case 'X':
     330                    case 'Y': case 'Z':
     331                    case '-':
     332                    case 'a': case 'b': case 'c': case 'd': case 'e': case 'f':
     333                    case 'g': case 'h': case 'i': case 'j': case 'k': case 'l':
     334                    case 'm': case 'n': case 'o': case 'p': case 'q': case 'r':
     335                    case 's': case 't': case 'u': case 'v': case 'w': case 'x':
     336                    case 'y': case 'z':
     337                    case '0': case '1': case '2': case '3': case '4':
     338                    case '5': case '6': case '7': case '8': case '9':
     339                      continue;
     340  
     341                    case '[':
     342                      /* Finish the key part and start the locale part.  */
     343                      APPEND (0);
     344                      found_locale = true;
     345                      locale_start = bufpos;
     346  
     347                      for (;;)
     348                        {
     349                          int c2 = phase2_getc ();
     350                          if (c2 == EOF || c2 == ']')
     351                            break;
     352                          APPEND (c2);
     353                        }
     354                      break;
     355  
     356                    default:
     357                      phase2_ungetc (c);
     358                      break;
     359                    }
     360                  break;
     361                }
     362              APPEND (0);
     363  
     364              /* Skip any space before '='.  */
     365              for (;;)
     366                {
     367                  c = phase2_getc ();
     368                  switch (c)
     369                    {
     370                    case ' ':
     371                      continue;
     372                    default:
     373                      phase2_ungetc (c);
     374                      break;
     375                    case EOF: case '\n':
     376                      break;
     377                    }
     378                  break;
     379                }
     380  
     381              c = phase2_getc ();
     382              if (c != '=')
     383                {
     384                  po_xerror (PO_SEVERITY_WARNING, NULL,
     385                             real_file_name, gram_pos.line_number, 0, false,
     386                             xasprintf (_("missing '=' after \"%s\""), buffer));
     387                  for (;;)
     388                    {
     389                      c = phase2_getc ();
     390                      if (c == EOF || c == '\n')
     391                        break;
     392                    }
     393                  tp->type = token_type_other;
     394                  return;
     395                }
     396  
     397              /* Skip any space after '='.  */
     398              for (;;)
     399                {
     400                  c = phase2_getc ();
     401                  switch (c)
     402                    {
     403                    case ' ':
     404                      continue;
     405                    default:
     406                      phase2_ungetc (c);
     407                      break;
     408                    case EOF:
     409                      break;
     410                    }
     411                  break;
     412                }
     413  
     414              value_start = bufpos;
     415              for (;;)
     416                {
     417                  c = phase2_getc ();
     418                  if (c == EOF || c == '\n')
     419                    break;
     420                  APPEND (c);
     421                }
     422              APPEND (0);
     423              tp->type = token_type_pair;
     424              tp->string = xmemdup (buffer, bufpos);
     425              tp->locale = found_locale ? &buffer[locale_start] : NULL;
     426              tp->value = &buffer[value_start];
     427              return;
     428            }
     429          default:
     430            {
     431              bool non_blank = false;
     432  
     433              for (;;)
     434                {
     435                  if (c == '\n' || c == EOF)
     436                    break;
     437  
     438                  if (!c_isspace (c))
     439                    non_blank = true;
     440                  else
     441                    APPEND (c);
     442  
     443                  c = phase2_getc ();
     444                }
     445              if (non_blank)
     446                {
     447                  po_xerror (PO_SEVERITY_WARNING, NULL,
     448                             real_file_name, gram_pos.line_number, 0, false,
     449                             _("invalid non-blank line"));
     450                  tp->type = token_type_other;
     451                  return;
     452                }
     453              APPEND (0);
     454              tp->type = token_type_blank;
     455              tp->string = xstrdup (buffer);
     456              return;
     457            }
     458          }
     459      }
     460  #undef APPEND
     461  }
     462  
     463  void
     464  desktop_parse (desktop_reader_ty *reader, FILE *file,
     465                 const char *real_filename, const char *logical_filename)
     466  {
     467    fp = file;
     468    real_file_name = real_filename;
     469    gram_pos.file_name = xstrdup (logical_filename);
     470    gram_pos.line_number = 1;
     471  
     472    for (;;)
     473      {
     474        struct token_ty token;
     475        desktop_lex (&token);
     476        switch (token.type)
     477          {
     478          case token_type_eof:
     479            goto out;
     480          case token_type_group:
     481            desktop_reader_handle_group (reader, token.string);
     482            break;
     483          case token_type_comment:
     484            desktop_reader_handle_comment (reader, token.string);
     485            break;
     486          case token_type_pair:
     487            desktop_reader_handle_pair (reader, &gram_pos,
     488                                        token.string, token.locale, token.value);
     489            break;
     490          case token_type_blank:
     491            desktop_reader_handle_blank (reader, token.string);
     492            break;
     493          case token_type_other:
     494            break;
     495          }
     496        free_token (&token);
     497      }
     498  
     499   out:
     500    fp = NULL;
     501    real_file_name = NULL;
     502    gram_pos.line_number = 0;
     503  }
     504  
     505  char *
     506  desktop_escape_string (const char *s, bool is_list)
     507  {
     508    char *buffer, *p;
     509  
     510    p = buffer = XNMALLOC (strlen (s) * 2 + 1, char);
     511  
     512    /* The first character must not be a whitespace.  */
     513    if (*s == ' ')
     514      {
     515        p = stpcpy (p, "\\s");
     516        s++;
     517      }
     518    else if (*s == '\t')
     519      {
     520        p = stpcpy (p, "\\t");
     521        s++;
     522      }
     523  
     524    for (;; s++)
     525      {
     526        if (*s == '\0')
     527          {
     528            *p = '\0';
     529            break;
     530          }
     531  
     532        switch (*s)
     533          {
     534          case '\n':
     535            p = stpcpy (p, "\\n");
     536            break;
     537          case '\r':
     538            p = stpcpy (p, "\\r");
     539            break;
     540          case '\\':
     541            if (is_list && *(s + 1) == ';')
     542              {
     543                p = stpcpy (p, "\\;");
     544                s++;
     545              }
     546            else
     547              p = stpcpy (p, "\\\\");
     548            break;
     549          default:
     550            *p++ = *s;
     551            break;
     552          }
     553      }
     554  
     555    return buffer;
     556  }
     557  
     558  char *
     559  desktop_unescape_string (const char *s, bool is_list)
     560  {
     561    char *buffer, *p;
     562  
     563    p = buffer = XNMALLOC (strlen (s) + 1, char);
     564    for (;; s++)
     565      {
     566        if (*s == '\0')
     567          {
     568            *p = '\0';
     569            break;
     570          }
     571  
     572        if (*s == '\\')
     573          {
     574            s++;
     575  
     576            if (*s == '\0')
     577              {
     578                *p = '\0';
     579                break;
     580              }
     581  
     582            switch (*s)
     583              {
     584              case 's':
     585                *p++ = ' ';
     586                break;
     587              case 'n':
     588                *p++ = '\n';
     589                break;
     590              case 't':
     591                *p++ = '\t';
     592                break;
     593              case 'r':
     594                *p++ = '\r';
     595                break;
     596              case ';':
     597                p = stpcpy (p, "\\;");
     598                break;
     599              default:
     600                *p++ = *s;
     601                break;
     602              }
     603          }
     604        else
     605          *p++ = *s;
     606      }
     607    return buffer;
     608  }
     609  
     610  void
     611  desktop_add_keyword (hash_table *keywords, const char *name, bool is_list)
     612  {
     613    hash_insert_entry (keywords, name, strlen (name), (void *) is_list);
     614  }
     615  
     616  void
     617  desktop_add_default_keywords (hash_table *keywords)
     618  {
     619    /* When adding new keywords here, also update the documentation in
     620       xgettext.texi!  */
     621    desktop_add_keyword (keywords, "Name", false);
     622    desktop_add_keyword (keywords, "GenericName", false);
     623    desktop_add_keyword (keywords, "Comment", false);
     624  #if 0 /* Icon values are localizable, but not supported by xgettext.  */
     625    desktop_add_keyword (keywords, "Icon", false);
     626  #endif
     627    desktop_add_keyword (keywords, "Keywords", true);
     628  }