Coverage Report

Created: 2025-12-14 07:02

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/gnupg/common/helpfile.c
Line
Count
Source
1
/* helpfile.c - GnuPG's helpfile feature
2
 *  Copyright (C) 2007 Free Software Foundation, Inc.
3
 *  Copyright (C) 2011,2012, 2025 g10 Code GmbH
4
 *
5
 * This file is part of GnuPG.
6
 *
7
 * This file is free software; you can redistribute it and/or modify
8
 * it under the terms of either
9
 *
10
 *   - the GNU Lesser General Public License as published by the Free
11
 *     Software Foundation; either version 3 of the License, or (at
12
 *     your option) any later version.
13
 *
14
 * or
15
 *
16
 *   - the GNU General Public License as published by the Free
17
 *     Software Foundation; either version 2 of the License, or (at
18
 *     your option) any later version.
19
 *
20
 * or both in parallel, as here.
21
 *
22
 * This file is distributed in the hope that it will be useful,
23
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
24
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
25
 * GNU General Public License for more details.
26
 *
27
 * You should have received a copy of the GNU General Public License
28
 * along with this program; if not, see <https://www.gnu.org/licenses/>.
29
 * SPDX-License-Identifier: (LGPL-3.0-or-later OR GPL-2.0-or-later)
30
 */
31
32
#include <config.h>
33
#include <stdlib.h>
34
35
36
#include "util.h"
37
#include "i18n.h"
38
#include "membuf.h"
39
40
41
/* Try to find KEY in the file FNAME.  */
42
static char *
43
findkey_fname (const char *key, const char *fname, unsigned int flags)
44
0
{
45
0
  gpg_error_t err = 0;
46
0
  estream_t fp;
47
0
  int lnr = 0;
48
0
  int c;
49
0
  char *p, line[256];
50
0
  int in_item = 0;
51
0
  membuf_t mb = MEMBUF_ZERO;
52
53
0
  fp = es_fopen (fname, "r");
54
0
  if (!fp)
55
0
    {
56
0
      if (errno != ENOENT)
57
0
        {
58
0
          err = gpg_error_from_syserror ();
59
0
          log_error (_("can't open '%s': %s\n"), fname, gpg_strerror (err));
60
0
        }
61
0
      return NULL;
62
0
    }
63
64
0
  while (es_fgets (line, DIM(line)-1, fp))
65
0
    {
66
0
      lnr++;
67
68
0
      if (!*line || line[strlen(line)-1] != '\n')
69
0
        {
70
          /* Eat until end of line. */
71
0
          while ((c = es_getc (fp)) != EOF && c != '\n')
72
0
            ;
73
0
          err = gpg_error (*line? GPG_ERR_LINE_TOO_LONG
74
0
                           : GPG_ERR_INCOMPLETE_LINE);
75
0
          log_error (_("file '%s', line %d: %s\n"),
76
0
                     fname, lnr, gpg_strerror (err));
77
0
        }
78
0
      else
79
0
        line[strlen(line)-1] = 0; /* Chop the LF. */
80
81
0
    again:
82
0
      if (!in_item)
83
0
        {
84
          /* Allow for empty lines and spaces while not in an item. */
85
0
          for (p=line; spacep (p); p++)
86
0
            ;
87
0
          if (!*p || *p == '#')
88
0
            continue;
89
0
          if (*line != '.' || spacep(line+1))
90
0
            {
91
0
              log_info (_("file '%s', line %d: %s\n"),
92
0
                        fname, lnr, _("ignoring garbage line"));
93
0
              continue;
94
0
            }
95
0
          trim_trailing_spaces (line);
96
0
          in_item = 1;
97
0
          if (!strcmp (line+1, key))
98
0
            {
99
              /* Found.  Start collecting.  */
100
0
              init_membuf (&mb, 1024);
101
0
            }
102
0
          continue;
103
0
        }
104
105
      /* If in an item only allow for comments in the first column
106
         and provide ". " as an escape sequence to allow for
107
         leading dots and hash marks in the actual text.  */
108
0
      if (*line == '#')
109
0
        continue;
110
0
      if (*line == '.')
111
0
        {
112
0
          if (spacep(line+1))
113
0
            p = line + 2;
114
0
          else
115
0
            {
116
0
              trim_trailing_spaces (line);
117
0
              in_item = 0;
118
0
              if (is_membuf_ready (&mb))
119
0
                break;        /* Yep, found and collected the item.  */
120
0
              if (!line[1])
121
0
                continue;     /* Just an end of text dot. */
122
0
              goto again;     /* A new key line.  */
123
0
            }
124
0
        }
125
0
      else
126
0
        p = line;
127
128
0
      if (is_membuf_ready (&mb))
129
0
        {
130
0
          put_membuf_str (&mb, p);
131
0
          if ((flags & GET_TEMPLATE_CRLF))
132
0
            put_membuf (&mb, "\r\n", 2);
133
0
          else
134
0
            put_membuf (&mb, "\n", 1);
135
0
        }
136
137
0
    }
138
0
  if ( !err && es_ferror (fp) )
139
0
    {
140
0
      err = gpg_error_from_syserror ();
141
0
      log_error (_("error reading '%s', line %d: %s\n"),
142
0
                 fname, lnr, gpg_strerror (err));
143
0
    }
144
145
0
  es_fclose (fp);
146
0
  if (is_membuf_ready (&mb))
147
0
    {
148
      /* We have collected something.  */
149
0
      if (err)
150
0
        {
151
0
          xfree (get_membuf (&mb, NULL));
152
0
          return NULL;
153
0
        }
154
0
      else
155
0
        {
156
0
          put_membuf (&mb, "", 1);  /* Terminate string.  */
157
0
          return get_membuf (&mb, NULL);
158
0
        }
159
0
    }
160
0
  else
161
0
    return NULL;
162
0
}
163
164
165
/* Try the help files depending on the locale.  */
166
static char *
167
findkey_locale (const char *domain, const char *key, const char *locname,
168
                const char *dirname, unsigned int flags)
169
0
{
170
0
  const char *s;
171
0
  char *fname, *ext, *p;
172
0
  char *result;
173
174
0
  fname = xtrymalloc (strlen (dirname) + 2
175
0
                      + strlen (domain) + strlen (locname) + 4 + 1);
176
0
  if (!fname)
177
0
    return NULL;
178
0
  ext = stpcpy (stpcpy (stpcpy (stpcpy (fname, dirname), "/"), domain), ".");
179
  /* Search with locale name and territory.  ("help.LL_TT.txt") */
180
0
  if (strchr (locname, '_'))
181
0
    {
182
0
      strcpy (stpcpy (ext, locname), ".txt");
183
0
      result = findkey_fname (key, fname, flags);
184
0
    }
185
0
  else
186
0
    result = NULL;  /* No territory.  */
187
188
0
  if (!result)
189
0
    {
190
      /* Search with just the locale name - if any. ("help.LL.txt") */
191
0
      if (*locname)
192
0
        {
193
0
          for (p=ext, s=locname; *s && *s != '_';)
194
0
            *p++ = *s++;
195
0
          strcpy (p, ".txt");
196
0
          result = findkey_fname (key, fname, flags);
197
0
        }
198
0
      else
199
0
        result = NULL;
200
0
    }
201
202
0
  if (!result && (!(flags & GET_TEMPLATE_CURRENT_LOCALE) || !*locname))
203
0
    {
204
      /* Last try: Search in file without any locale info.  ("help.txt") */
205
0
      strcpy (ext, "txt");
206
0
      result = findkey_fname (key, fname, flags);
207
0
    }
208
209
0
  xfree (fname);
210
0
  return result;
211
0
}
212
213
214
/* Return a malloced text as identified by KEY.  The system takes
215
   the string from an UTF-8 encoded file to be created by an
216
   administrator or as distributed with GnuPG.  On a GNU or Unix
217
   system the entry is searched in these files:
218
219
     /etc/gnupg/<domain>.<LL>.txt
220
     /etc/gnupg/<domain>.txt
221
     /usr/share/gnupg/<domain>.<LL>.txt
222
     /usr/share/gnupg/<domain>.txt
223
224
   The <domain> is either "help" or any other domain like "mail-tube".
225
   Here <LL> denotes the two digit language code of the current
226
   locale.  If the flag bit GET_TEMPLATE_CURRENT_LOCALE is set, the
227
   function won't fallback to the english valiant ("<domain>.txt")
228
   unless that locale has been requested.
229
230
   The template file needs to be encoded in UTF-8, lines with a '#' in the
231
   first column are comment lines and entirely ignored.  Help keys are
232
   identified by a key consisting of a single word with a single dot
233
   as the first character.  All key lines listed without any
234
   intervening lines (except for comment lines) lead to the same help
235
   text.  Lines following the key lines make up the actual template texts.
236
*/
237
char *
238
gnupg_get_template (const char *domain, const char *key, unsigned int flags,
239
                    const char *override_locale)
240
0
{
241
0
  static const char *locname_buffer;
242
0
  const char *locname;
243
0
  char *result;
244
245
0
  if (override_locale && *override_locale)
246
0
    locname = override_locale;
247
0
  else if (!locname_buffer)
248
0
    {
249
0
      char *buffer, *p;
250
0
      int count = 0;
251
0
      const char *s = gnupg_messages_locale_name ();
252
253
0
      buffer = xtrystrdup (s);
254
0
      if (!buffer)
255
0
        locname_buffer = "";
256
0
      else
257
0
        {
258
0
          for (p = buffer; *p; p++)
259
0
            if (*p == '.' || *p == '@' || *p == '/' /*(safeguard)*/)
260
0
              *p = 0;
261
0
            else if (*p == '_')
262
0
              {
263
0
                if (count++)
264
0
                  *p = 0;  /* Also cut at an underscore in the territory.  */
265
0
              }
266
0
          locname_buffer = buffer;
267
0
        }
268
0
      locname = locname_buffer;
269
0
    }
270
0
  else
271
0
    locname = locname_buffer;
272
273
0
  if (!key || !*key)
274
0
    return NULL;
275
276
0
  result = findkey_locale (domain, key, locname,
277
0
                           gnupg_sysconfdir (), flags);
278
0
  if (!result)
279
0
    result = findkey_locale (domain, key, locname,
280
0
                             gnupg_datadir (), flags);
281
282
0
  if (result && (flags & GET_TEMPLATE_SUBST_ENVVARS))
283
0
    {
284
0
      char *tmp = substitute_envvars (result);
285
0
      if (tmp)
286
0
        {
287
0
          xfree (result);
288
0
          result = tmp;
289
0
        }
290
0
    }
291
292
0
  if (result && !(flags & GET_TEMPLATE_CRLF))
293
0
    trim_trailing_spaces (result);
294
295
0
  return result;
296
0
}
297
298
299
char *
300
gnupg_get_help_string (const char *key, int only_current)
301
0
{
302
0
  return gnupg_get_template ("help", key,
303
0
                             only_current? GET_TEMPLATE_CURRENT_LOCALE : 0,
304
                             NULL);
305
0
}