summaryrefslogtreecommitdiffstats
path: root/lib/dpkg/parsehelp.c
blob: fd60b07ee9f5bcb1fe41656b8989b36352a52fed (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
/*
 * libdpkg - Debian packaging suite library routines
 * parsehelp.c - helpful routines for parsing and writing
 *
 * Copyright © 1995 Ian Jackson <ijackson@chiark.greenend.org.uk>
 * Copyright © 2006-2012 Guillem Jover <guillem@debian.org>
 *
 * This is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 */

#include <config.h>
#include <compat.h>

#include <errno.h>
#include <limits.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>

#include <dpkg/i18n.h>
#include <dpkg/c-ctype.h>
#include <dpkg/dpkg.h>
#include <dpkg/dpkg-db.h>
#include <dpkg/string.h>
#include <dpkg/error.h>
#include <dpkg/parsedump.h>

static DPKG_ATTR_VPRINTF(2) const char *
parse_error_msg(struct parsedb_state *ps, const char *fmt, va_list args)
{
  struct varbuf *vb = &ps->errmsg;

  varbuf_reset(vb);

  if (ps->pkg && ps->pkg->set->name)
    varbuf_printf(vb, _("parsing file '%s' near line %d package '%s':\n "),
                  ps->filename, ps->lno,
                  pkgbin_name(ps->pkg, ps->pkgbin, pnaw_nonambig));
  else
    varbuf_printf(vb, _("parsing file '%.255s' near line %d:\n "),
                  ps->filename, ps->lno);

  varbuf_vprintf(vb, fmt, args);

  return vb->buf;
}

void
parse_error(struct parsedb_state *ps, const char *fmt, ...)
{
  va_list args;
  const char *str;

  va_start(args, fmt);
  str = parse_error_msg(ps, fmt, args);
  va_end(args);

  ohshit("%s", str);
}

void
parse_warn(struct parsedb_state *ps, const char *fmt, ...)
{
  va_list args;

  va_start(args, fmt);
  warning("%s", parse_error_msg(ps, fmt, args));
  va_end(args);
}

void
parse_problem(struct parsedb_state *ps, const char *fmt, ...)
{
  va_list args;
  const char *str;

  va_start(args, fmt);
  str = parse_error_msg(ps, fmt, args);
  va_end(args);

  if (ps->err.type == DPKG_MSG_WARN)
    warning("%s: %s", str, ps->err.str);
  else
    ohshit("%s: %s", str, ps->err.str);
}

const struct fieldinfo *
find_field_info(const struct fieldinfo *fields, const char *fieldname)
{
  const struct fieldinfo *field;

  for (field = fields; field->name; field++)
    if (strcasecmp(field->name, fieldname) == 0)
      return field;

  return NULL;
}

const struct arbitraryfield *
find_arbfield_info(const struct arbitraryfield *arbs, const char *fieldname)
{
  const struct arbitraryfield *arbfield;

  for (arbfield = arbs; arbfield; arbfield = arbfield->next)
    if (strcasecmp(arbfield->name, fieldname) == 0)
      return arbfield;

  return NULL;
}

const char *
pkg_name_is_illegal(const char *p)
{
  /* TODO: _ is deprecated, remove sometime. */
  static const char alsoallowed[] = "-+._";
  static char buf[150];
  int c;

  if (!*p) return _("may not be empty string");
  if (!c_isalnum(*p))
    return _("must start with an alphanumeric character");
  while ((c = *p++) != '\0')
    if (!c_isalnum(c) && !strchr(alsoallowed, c))
      break;
  if (!c) return NULL;

  snprintf(buf, sizeof(buf), _(
	   "character '%c' not allowed (only letters, digits and characters '%s')"),
	   c, alsoallowed);
  return buf;
}

void varbufversion
(struct varbuf *vb,
 const struct dpkg_version *version,
 enum versiondisplayepochwhen vdew)
{
  switch (vdew) {
  case vdew_never:
    break;
  case vdew_nonambig:
    if (!version->epoch &&
        (!version->version || !strchr(version->version,':')) &&
        (!version->revision || !strchr(version->revision,':'))) break;
    /* Fall through. */
  case vdew_always:
    varbuf_printf(vb, "%u:", version->epoch);
    break;
  default:
    internerr("unknown versiondisplayepochwhen '%d'", vdew);
  }
  if (version->version)
    varbuf_add_str(vb, version->version);
  if (str_is_set(version->revision)) {
    varbuf_add_char(vb, '-');
    varbuf_add_str(vb, version->revision);
  }
}

const char *versiondescribe
(const struct dpkg_version *version,
 enum versiondisplayepochwhen vdew)
{
  static struct varbuf bufs[10];
  static int bufnum=0;

  struct varbuf *vb;

  if (!dpkg_version_is_informative(version))
    return C_("version", "<none>");

  vb= &bufs[bufnum]; bufnum++; if (bufnum == 10) bufnum= 0;
  varbuf_reset(vb);
  varbufversion(vb,version,vdew);
  varbuf_end_str(vb);

  return vb->buf;
}

const char *
versiondescribe_c(const struct dpkg_version *version,
                  enum versiondisplayepochwhen vdew)
{
  struct dpkg_locale oldloc;
  const char *str;

  oldloc = dpkg_locale_switch_C();
  str = versiondescribe(version, vdew);
  dpkg_locale_switch_back(oldloc);

  return str;
}

/**
 * Parse a version string and check for invalid syntax.
 *
 * Distinguish between lax (warnings) and strict (error) parsing.
 *
 * @param rversion The parsed version.
 * @param string The version string to parse.
 * @param err The warning or error message if any.
 *
 * @retval  0 On success.
 * @retval -1 On failure, and err is set accordingly.
 */
int
parseversion(struct dpkg_version *rversion, const char *string,
             struct dpkg_error *err)
{
  char *hyphen, *colon, *eepochcolon;
  const char *end, *ptr;

  /* Trim leading and trailing space. */
  while (*string && c_isblank(*string))
    string++;

  if (!*string)
    return dpkg_put_error(err, _("version string is empty"));

  /* String now points to the first non-whitespace char. */
  end = string;
  /* Find either the end of the string, or a whitespace char. */
  while (*end && !c_isblank(*end))
    end++;
  /* Check for extra chars after trailing space. */
  ptr = end;
  while (*ptr && c_isblank(*ptr))
    ptr++;
  if (*ptr)
    return dpkg_put_error(err, _("version string has embedded spaces"));

  colon= strchr(string,':');
  if (colon) {
    long epoch;

    errno = 0;
    epoch = strtol(string, &eepochcolon, 10);
    if (string == eepochcolon)
      return dpkg_put_error(err, _("epoch in version is empty"));
    if (colon != eepochcolon)
      return dpkg_put_error(err, _("epoch in version is not number"));
    if (epoch < 0)
      return dpkg_put_error(err, _("epoch in version is negative"));
    if (epoch > INT_MAX || errno == ERANGE)
      return dpkg_put_error(err, _("epoch in version is too big"));
    if (!*++colon)
      return dpkg_put_error(err, _("nothing after colon in version number"));
    string= colon;
    rversion->epoch= epoch;
  } else {
    rversion->epoch= 0;
  }
  rversion->version= nfstrnsave(string,end-string);
  hyphen= strrchr(rversion->version,'-');
  if (hyphen) {
    *hyphen++ = '\0';

    if (*hyphen == '\0')
      return dpkg_put_error(err, _("revision number is empty"));
  }
  rversion->revision= hyphen ? hyphen : "";

  /* XXX: Would be faster to use something like cisversion and cisrevision. */
  ptr = rversion->version;
  if (!*ptr)
    return dpkg_put_error(err, _("version number is empty"));
  if (*ptr && !c_isdigit(*ptr++))
    return dpkg_put_warn(err, _("version number does not start with digit"));
  for (; *ptr; ptr++) {
    if (!c_isdigit(*ptr) && !c_isalpha(*ptr) && strchr(".-+~:", *ptr) == NULL)
      return dpkg_put_warn(err, _("invalid character in version number"));
  }
  for (ptr = rversion->revision; *ptr; ptr++) {
    if (!c_isdigit(*ptr) && !c_isalpha(*ptr) && strchr(".+~", *ptr) == NULL)
      return dpkg_put_warn(err, _("invalid character in revision number"));
  }

  return 0;
}

/**
 * Parse a version string coming from a database file.
 *
 * It parses a version string, and prints a warning or an error depending
 * on the parse options.
 *
 * @param ps The parsedb state.
 * @param version The version to parse into.
 * @param value The version string to parse from.
 *
 * @retval  0 On success, and err is reset.
 * @retval -1 On failure, and err is set accordingly.
 */
int
parse_db_version(struct parsedb_state *ps, struct dpkg_version *version,
                 const char *value)
{
  dpkg_error_destroy(&ps->err);

  if (parseversion(version, value, &ps->err) == 0)
    return 0;

  /* If not in lax mode, turn everything into an error. */
  if (!(ps->flags & pdb_lax_version_parser))
    ps->err.type = DPKG_MSG_ERROR;

  return -1;
}

void
parse_must_have_field(struct parsedb_state *ps,
                      const char *value, const char *what)
{
  if (!value)
    parse_error(ps, _("missing '%s' field"), what);
  else if (!*value)
    parse_error(ps, _("empty value for '%s' field"), what);
}

void
parse_ensure_have_field(struct parsedb_state *ps,
                        const char **value, const char *what)
{
  static const char empty[] = "";

  if (!*value) {
    parse_warn(ps, _("missing '%s' field"), what);
    *value = empty;
  } else if (!**value) {
    parse_warn(ps, _("empty value for '%s' field"), what);
  }
}