Coverage Report

Created: 2025-11-24 06:30

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/cups/cups/options.c
Line
Count
Source
1
/*
2
 * Option routines for CUPS.
3
 *
4
 * Copyright © 2020-2025 by OpenPrinting.
5
 * Copyright © 2007-2017 by Apple Inc.
6
 * Copyright © 1997-2007 by Easy Software Products.
7
 *
8
 * Licensed under Apache License v2.0.  See the file "LICENSE" for more information.
9
 */
10
11
#include "cups-private.h"
12
#include "debug-internal.h"
13
14
15
/*
16
 * Local functions...
17
 */
18
19
static int  cups_compare_options(cups_option_t *a, cups_option_t *b);
20
static int  cups_find_option(const char *name, int num_options,
21
                           cups_option_t *option, int prev, int *rdiff);
22
23
24
/*
25
 * 'cupsAddIntegerOption()' - Add an integer option to an option array.
26
 *
27
 * New option arrays can be initialized simply by passing 0 for the
28
 * "num_options" parameter.
29
 *
30
 * @since CUPS 2.2.4@
31
 */
32
33
int         /* O  - Number of options */
34
cupsAddIntegerOption(
35
    const char    *name,    /* I  - Name of option */
36
    int           value,    /* I  - Value of option */
37
    int           num_options,    /* I  - Number of options */
38
    cups_option_t **options)    /* IO - Pointer to options */
39
0
{
40
0
  char  strvalue[32];     /* String value */
41
42
43
0
  snprintf(strvalue, sizeof(strvalue), "%d", value);
44
45
0
  return (cupsAddOption(name, strvalue, num_options, options));
46
0
}
47
48
49
/*
50
 * 'cupsAddOption()' - Add an option to an option array.
51
 *
52
 * New option arrays can be initialized simply by passing 0 for the
53
 * "num_options" parameter.
54
 */
55
56
int         /* O  - Number of options */
57
cupsAddOption(const char    *name,  /* I  - Name of option */
58
              const char    *value, /* I  - Value of option */
59
        int           num_options,/* I  - Number of options */
60
              cups_option_t **options)  /* IO - Pointer to options */
61
46.1k
{
62
46.1k
  cups_option_t *temp;      /* Pointer to new option */
63
46.1k
  int   insert,     /* Insertion point */
64
46.1k
    diff;     /* Result of search */
65
66
67
46.1k
  DEBUG_printf("2cupsAddOption(name=\"%s\", value=\"%s\", num_options=%d, options=%p)", name, value, num_options, (void *)options);
68
69
46.1k
  if (!name || !name[0] || !value || !options || num_options < 0)
70
479
  {
71
479
    DEBUG_printf("3cupsAddOption: Returning %d", num_options);
72
479
    return (num_options);
73
479
  }
74
75
45.6k
  if (!_cups_strcasecmp(name, "cupsPrintQuality"))
76
0
    num_options = cupsRemoveOption("print-quality", num_options, options);
77
45.6k
  else if (!_cups_strcasecmp(name, "print-quality"))
78
498
    num_options = cupsRemoveOption("cupsPrintQuality", num_options, options);
79
80
 /*
81
  * Look for an existing option with the same name...
82
  */
83
84
45.6k
  if (num_options == 0)
85
18.6k
  {
86
18.6k
    insert = 0;
87
18.6k
    diff   = 1;
88
18.6k
  }
89
26.9k
  else
90
26.9k
  {
91
26.9k
    insert = cups_find_option(name, num_options, *options, num_options - 1,
92
26.9k
                              &diff);
93
94
26.9k
    if (diff > 0)
95
6.00k
      insert ++;
96
26.9k
  }
97
98
45.6k
  if (diff)
99
41.0k
  {
100
   /*
101
    * No matching option name...
102
    */
103
104
41.0k
    DEBUG_printf("4cupsAddOption: New option inserted at index %d...", insert);
105
106
41.0k
    if (num_options == 0)
107
18.6k
      temp = (cups_option_t *)malloc(sizeof(cups_option_t));
108
22.3k
    else
109
22.3k
      temp = (cups_option_t *)realloc(*options, sizeof(cups_option_t) * (size_t)(num_options + 1));
110
111
41.0k
    if (!temp)
112
0
    {
113
0
      DEBUG_puts("3cupsAddOption: Unable to expand option array, returning 0");
114
0
      return (0);
115
0
    }
116
117
41.0k
    *options = temp;
118
119
41.0k
    if (insert < num_options)
120
16.3k
    {
121
16.3k
      DEBUG_printf("4cupsAddOption: Shifting %d options...", (int)(num_options - insert));
122
16.3k
      memmove(temp + insert + 1, temp + insert, (size_t)(num_options - insert) * sizeof(cups_option_t));
123
16.3k
    }
124
125
41.0k
    temp        += insert;
126
41.0k
    temp->name  = _cupsStrAlloc(name);
127
41.0k
    num_options ++;
128
41.0k
  }
129
4.58k
  else
130
4.58k
  {
131
   /*
132
    * Match found; free the old value...
133
    */
134
135
4.58k
    DEBUG_printf("4cupsAddOption: Option already exists at index %d...", insert);
136
137
4.58k
    temp = *options + insert;
138
4.58k
    _cupsStrFree(temp->value);
139
4.58k
  }
140
141
45.6k
  temp->value = _cupsStrAlloc(value);
142
143
45.6k
  DEBUG_printf("3cupsAddOption: Returning %d", num_options);
144
145
45.6k
  return (num_options);
146
45.6k
}
147
148
149
/*
150
 * 'cupsFreeOptions()' - Free all memory used by options.
151
 */
152
153
void
154
cupsFreeOptions(
155
    int           num_options,    /* I - Number of options */
156
    cups_option_t *options)   /* I - Pointer to options */
157
19.1k
{
158
19.1k
  int i;        /* Looping var */
159
160
161
19.1k
  DEBUG_printf("cupsFreeOptions(num_options=%d, options=%p)", num_options, (void *)options);
162
163
19.1k
  if (num_options <= 0 || !options)
164
6.35k
    return;
165
166
47.9k
  for (i = 0; i < num_options; i ++)
167
35.1k
  {
168
35.1k
    _cupsStrFree(options[i].name);
169
35.1k
    _cupsStrFree(options[i].value);
170
35.1k
  }
171
172
12.7k
  free(options);
173
12.7k
}
174
175
176
/*
177
 * 'cupsGetIntegerOption()' - Get an integer option value.
178
 *
179
 * INT_MIN is returned when the option does not exist, is not an integer, or
180
 * exceeds the range of values for the "int" type.
181
 *
182
 * @since CUPS 2.2.4@
183
 */
184
185
int         /* O - Option value or @code INT_MIN@ */
186
cupsGetIntegerOption(
187
    const char    *name,    /* I - Name of option */
188
    int           num_options,    /* I - Number of options */
189
    cups_option_t *options)   /* I - Options */
190
0
{
191
0
  const char  *value = cupsGetOption(name, num_options, options);
192
          /* String value of option */
193
0
  char    *ptr;     /* Pointer into string value */
194
0
  long    intvalue;   /* Integer value */
195
196
197
0
  if (!value || !*value)
198
0
    return (INT_MIN);
199
200
0
  intvalue = strtol(value, &ptr, 10);
201
0
  if (intvalue < INT_MIN || intvalue > INT_MAX || *ptr)
202
0
    return (INT_MIN);
203
204
0
  return ((int)intvalue);
205
0
}
206
207
208
/*
209
 * 'cupsGetOption()' - Get an option value.
210
 */
211
212
const char *        /* O - Option value or @code NULL@ */
213
cupsGetOption(const char    *name,  /* I - Name of option */
214
              int           num_options,/* I - Number of options */
215
              cups_option_t *options) /* I - Options */
216
203k
{
217
203k
  int diff,       /* Result of comparison */
218
203k
  match;        /* Matching index */
219
220
221
203k
  DEBUG_printf("2cupsGetOption(name=\"%s\", num_options=%d, options=%p)", name, num_options, (void *)options);
222
223
203k
  if (!name || num_options <= 0 || !options)
224
105k
  {
225
105k
    DEBUG_puts("3cupsGetOption: Returning NULL");
226
105k
    return (NULL);
227
105k
  }
228
229
97.7k
  match = cups_find_option(name, num_options, options, -1, &diff);
230
231
97.7k
  if (!diff)
232
224
  {
233
224
    DEBUG_printf("3cupsGetOption: Returning \"%s\"", options[match].value);
234
224
    return (options[match].value);
235
224
  }
236
237
97.5k
  DEBUG_puts("3cupsGetOption: Returning NULL");
238
97.5k
  return (NULL);
239
97.7k
}
240
241
242
/*
243
 * 'cupsParseOptions()' - Parse options from a command-line argument.
244
 *
245
 * This function converts space-delimited name/value pairs according
246
 * to the PAPI text option ABNF specification. Collection values
247
 * ("name={a=... b=... c=...}") are stored with the curley brackets
248
 * intact - use @code cupsParseOptions@ on the value to extract the
249
 * collection attributes.
250
 */
251
252
int         /* O - Number of options found */
253
cupsParseOptions(
254
    const char    *arg,     /* I - Argument to parse */
255
    int           num_options,    /* I - Number of options */
256
    cups_option_t **options)    /* O - Options found */
257
19.1k
{
258
19.1k
  return (cupsParseOptions2(arg, /*end*/NULL, num_options, options));
259
19.1k
}
260
261
262
//
263
// 'cupsParseOptions2()' - Parse options from a command-line argument.
264
//
265
// This function converts space-delimited name/value pairs according
266
// to the PAPI text option ABNF specification. Collection values
267
// ("name={a=... b=... c=...}") are stored with the curley brackets
268
// intact - use @code cupsParseOptions@ on the value to extract the
269
// collection attributes.
270
//
271
// The "end" argument, if not `NULL`, receives a pointer to the end of the
272
// options.
273
//
274
// @since CUPS 2.5@
275
//
276
277
int         // O - Number of options found
278
cupsParseOptions2(
279
    const char    *arg,     // I - Argument to parse
280
    const char    **end,    // O - Pointer to end of options or `NULL` for "don't care"
281
    int           num_options,    // I - Number of options
282
    cups_option_t **options)    // O - Options found
283
19.1k
{
284
19.1k
  char  *copyarg,     // Copy of input string
285
19.1k
  *ptr,       // Pointer into string
286
19.1k
  *name,        // Pointer to name
287
19.1k
  *value,       // Pointer to value
288
19.1k
  sep,        // Separator character
289
19.1k
  quote;        // Quote character
290
291
292
  // Range check input...
293
19.1k
  if (end)
294
0
    *end = NULL;
295
296
19.1k
  if (!arg)
297
0
    return (num_options);
298
299
19.1k
  if (!options)
300
0
    return (0);
301
302
  // Make a copy of the argument string and then divide it up...
303
19.1k
  if ((copyarg = strdup(arg)) == NULL)
304
0
  {
305
0
    DEBUG_puts("1cupsParseOptions2: Unable to copy arg string");
306
0
    return (num_options);
307
0
  }
308
309
19.1k
  if (*copyarg == '{')
310
12.6k
    ptr = copyarg + 1;
311
6.49k
  else
312
6.49k
    ptr = copyarg;
313
314
  // Skip leading spaces...
315
35.8k
  while (_cups_isspace(*ptr))
316
16.7k
    ptr ++;
317
318
  // Loop through the string...
319
65.2k
  while (*ptr != '\0')
320
46.3k
  {
321
    // Get the name up to a SPACE, =, or end-of-string...
322
46.3k
    name = ptr;
323
261k
    while (!strchr("\f\n\r\t\v =", *ptr) && *ptr)
324
215k
      ptr ++;
325
326
    // Avoid an empty name...
327
46.3k
    if (ptr == name)
328
229
      break;
329
330
    // End after the closing brace...
331
46.1k
    if (*ptr == '}' && *copyarg == '{')
332
0
    {
333
0
      *ptr++ = '\0';
334
0
      break;
335
0
    }
336
337
    // Skip trailing spaces...
338
80.7k
    while (_cups_isspace(*ptr))
339
34.6k
      *ptr++ = '\0';
340
341
46.1k
    if ((sep = *ptr) == '=')
342
14.1k
      *ptr++ = '\0';
343
344
46.1k
    if (sep != '=')
345
31.9k
    {
346
      // Boolean option...
347
31.9k
      if (!_cups_strncasecmp(name, "no", 2))
348
1.31k
        num_options = cupsAddOption(name + 2, "false", num_options, options);
349
30.6k
      else
350
30.6k
        num_options = cupsAddOption(name, "true", num_options, options);
351
352
31.9k
      continue;
353
31.9k
    }
354
355
    // Remove = and parse the value...
356
14.1k
    value = ptr;
357
358
30.8k
    while (*ptr && !_cups_isspace(*ptr))
359
16.6k
    {
360
16.6k
      if (*ptr == ',')
361
482
      {
362
482
        ptr ++;
363
482
      }
364
16.1k
      else if (*ptr == '\'' || *ptr == '\"')
365
1.66k
      {
366
        // Quoted string constant...
367
1.66k
  quote = *ptr;
368
1.66k
  _cups_strcpy(ptr, ptr + 1);
369
370
10.1k
  while (*ptr != quote && *ptr)
371
8.44k
  {
372
8.44k
    if (*ptr == '\\' && ptr[1])
373
2.74k
      _cups_strcpy(ptr, ptr + 1);
374
375
8.44k
    ptr ++;
376
8.44k
  }
377
378
1.66k
  if (*ptr)
379
1.23k
    _cups_strcpy(ptr, ptr + 1);
380
1.66k
      }
381
14.5k
      else if (*ptr == '{')
382
645
      {
383
        // Collection value...
384
645
  int depth;      // Nesting depth for braces
385
386
8.24k
  for (depth = 0; *ptr; ptr ++)
387
7.91k
  {
388
7.91k
    if (*ptr == '{')
389
1.97k
    {
390
1.97k
      depth ++;
391
1.97k
    }
392
5.94k
    else if (*ptr == '}')
393
879
    {
394
879
      depth --;
395
879
      if (!depth)
396
321
      {
397
321
        ptr ++;
398
321
        break;
399
321
      }
400
879
    }
401
5.06k
    else if (*ptr == '\\' && ptr[1])
402
500
    {
403
500
      _cups_strcpy(ptr, ptr + 1);
404
500
    }
405
7.91k
  }
406
645
      }
407
13.8k
      else
408
13.8k
      {
409
        // Normal space-delimited string...
410
109k
  while (*ptr && !_cups_isspace(*ptr))
411
96.7k
  {
412
96.7k
    if (*ptr == '}' && *copyarg == '{')
413
1.22k
    {
414
1.22k
      *ptr++ = '\0';
415
1.22k
      break;
416
1.22k
    }
417
418
95.4k
    if (*ptr == '\\' && ptr[1])
419
2.07k
      _cups_strcpy(ptr, ptr + 1);
420
421
95.4k
    ptr ++;
422
95.4k
  }
423
13.8k
      }
424
16.6k
    }
425
426
14.1k
    if (*ptr != '\0')
427
6.07k
      *ptr++ = '\0';
428
429
    // Skip trailing whitespace...
430
19.5k
    while (_cups_isspace(*ptr))
431
5.40k
      ptr ++;
432
433
    // Add the string value...
434
14.1k
    num_options = cupsAddOption(name, value, num_options, options);
435
14.1k
  }
436
437
  // Save the progress in the input string...
438
19.1k
  if (end)
439
0
    *end = arg + (ptr - copyarg);
440
441
  // Free the copy of the argument we made and return the number of options found.
442
19.1k
  free(copyarg);
443
444
19.1k
  return (num_options);
445
19.1k
}
446
447
448
/*
449
 * 'cupsRemoveOption()' - Remove an option from an option array.
450
 *
451
 * @since CUPS 1.2@
452
 */
453
454
int         /* O  - New number of options */
455
cupsRemoveOption(
456
    const char    *name,    /* I  - Option name */
457
    int           num_options,    /* I  - Current number of options */
458
    cups_option_t **options)    /* IO - Options */
459
498
{
460
498
  int   i;      /* Looping var */
461
498
  cups_option_t *option;    /* Current option */
462
463
464
498
  DEBUG_printf("2cupsRemoveOption(name=\"%s\", num_options=%d, options=%p)", name, num_options, (void *)options);
465
466
 /*
467
  * Range check input...
468
  */
469
470
498
  if (!name || num_options < 1 || !options)
471
210
  {
472
210
    DEBUG_printf("3cupsRemoveOption: Returning %d", num_options);
473
210
    return (num_options);
474
210
  }
475
476
 /*
477
  * Loop for the option...
478
  */
479
480
1.86k
  for (i = num_options, option = *options; i > 0; i --, option ++)
481
1.57k
    if (!_cups_strcasecmp(name, option->name))
482
0
      break;
483
484
288
  if (i)
485
0
  {
486
   /*
487
    * Remove this option from the array...
488
    */
489
490
0
    DEBUG_puts("4cupsRemoveOption: Found option, removing it...");
491
492
0
    num_options --;
493
0
    i --;
494
495
0
    _cupsStrFree(option->name);
496
0
    _cupsStrFree(option->value);
497
498
0
    if (i > 0)
499
0
      memmove(option, option + 1, (size_t)i * sizeof(cups_option_t));
500
0
  }
501
502
 /*
503
  * Return the new number of options...
504
  */
505
506
288
  DEBUG_printf("3cupsRemoveOption: Returning %d", num_options);
507
288
  return (num_options);
508
498
}
509
510
511
/*
512
 * '_cupsGet1284Values()' - Get 1284 device ID keys and values.
513
 *
514
 * The returned dictionary is a CUPS option array that can be queried with
515
 * cupsGetOption and freed with cupsFreeOptions.
516
 */
517
518
int         /* O - Number of key/value pairs */
519
_cupsGet1284Values(
520
    const char *device_id,    /* I - IEEE-1284 device ID string */
521
    cups_option_t **values)   /* O - Array of key/value pairs */
522
0
{
523
0
  int   num_values;   /* Number of values */
524
0
  char    key[256],   /* Key string */
525
0
    value[256],   /* Value string */
526
0
    *ptr;     /* Pointer into key/value */
527
528
529
 /*
530
  * Range check input...
531
  */
532
533
0
  if (values)
534
0
    *values = NULL;
535
536
0
  if (!device_id || !values)
537
0
    return (0);
538
539
 /*
540
  * Parse the 1284 device ID value into keys and values.  The format is
541
  * repeating sequences of:
542
  *
543
  *   [whitespace]key:value[whitespace];
544
  */
545
546
0
  num_values = 0;
547
0
  while (*device_id)
548
0
  {
549
0
    while (_cups_isspace(*device_id))
550
0
      device_id ++;
551
552
0
    if (!*device_id)
553
0
      break;
554
555
0
    for (ptr = key; *device_id && *device_id != ':'; device_id ++)
556
0
      if (ptr < (key + sizeof(key) - 1))
557
0
        *ptr++ = *device_id;
558
559
0
    if (!*device_id)
560
0
      break;
561
562
0
    while (ptr > key && _cups_isspace(ptr[-1]))
563
0
      ptr --;
564
565
0
    *ptr = '\0';
566
0
    device_id ++;
567
568
0
    while (_cups_isspace(*device_id))
569
0
      device_id ++;
570
571
0
    if (!*device_id)
572
0
      break;
573
574
0
    for (ptr = value; *device_id && *device_id != ';'; device_id ++)
575
0
      if (ptr < (value + sizeof(value) - 1))
576
0
        *ptr++ = *device_id;
577
578
0
    while (ptr > value && _cups_isspace(ptr[-1]))
579
0
      ptr --;
580
581
0
    *ptr = '\0';
582
0
    num_values = cupsAddOption(key, value, num_values, values);
583
584
0
    if (!*device_id)
585
0
      break;
586
0
    device_id ++;
587
0
  }
588
589
0
  return (num_values);
590
0
}
591
592
593
/*
594
 * 'cups_compare_options()' - Compare two options.
595
 */
596
597
static int        /* O - Result of comparison */
598
cups_compare_options(cups_option_t *a,  /* I - First option */
599
         cups_option_t *b)  /* I - Second option */
600
344k
{
601
344k
  return (_cups_strcasecmp(a->name, b->name));
602
344k
}
603
604
605
/*
606
 * 'cups_find_option()' - Find an option using a binary search.
607
 */
608
609
static int        /* O - Index of match */
610
cups_find_option(
611
    const char    *name,    /* I - Option name */
612
    int           num_options,    /* I - Number of options */
613
    cups_option_t *options,   /* I - Options */
614
    int           prev,     /* I - Previous index */
615
    int           *rdiff)   /* O - Difference of match */
616
124k
{
617
124k
  int   left,     /* Low mark for binary search */
618
124k
    right,      /* High mark for binary search */
619
124k
    current,    /* Current index */
620
124k
    diff;     /* Result of comparison */
621
124k
  cups_option_t key;      /* Search key */
622
623
624
124k
  DEBUG_printf("7cups_find_option(name=\"%s\", num_options=%d, options=%p, prev=%d, rdiff=%p)", name, num_options, (void *)options, prev, (void *)rdiff);
625
626
#ifdef DEBUG
627
  for (left = 0; left < num_options; left ++)
628
    DEBUG_printf("9cups_find_option: options[%d].name=\"%s\", .value=\"%s\"", left, options[left].name, options[left].value);
629
#endif /* DEBUG */
630
631
124k
  key.name = (char *)name;
632
633
124k
  if (prev >= 0)
634
26.9k
  {
635
   /*
636
    * Start search on either side of previous...
637
    */
638
639
26.9k
    if ((diff = cups_compare_options(&key, options + prev)) == 0 ||
640
25.6k
        (diff < 0 && prev == 0) ||
641
22.7k
  (diff > 0 && prev == (num_options - 1)))
642
10.2k
    {
643
10.2k
      *rdiff = diff;
644
10.2k
      return (prev);
645
10.2k
    }
646
16.7k
    else if (diff < 0)
647
16.7k
    {
648
     /*
649
      * Start with previous on right side...
650
      */
651
652
16.7k
      left  = 0;
653
16.7k
      right = prev;
654
16.7k
    }
655
0
    else
656
0
    {
657
     /*
658
      * Start with previous on left side...
659
      */
660
661
0
      left  = prev;
662
0
      right = num_options - 1;
663
0
    }
664
26.9k
  }
665
97.7k
  else
666
97.7k
  {
667
   /*
668
    * Start search in the middle...
669
    */
670
671
97.7k
    left  = 0;
672
97.7k
    right = num_options - 1;
673
97.7k
  }
674
675
114k
  do
676
138k
  {
677
138k
    current = (left + right) / 2;
678
138k
    diff    = cups_compare_options(&key, options + current);
679
680
138k
    if (diff == 0)
681
2.82k
      break;
682
135k
    else if (diff < 0)
683
59.0k
      right = current;
684
76.2k
    else
685
76.2k
      left = current;
686
138k
  }
687
135k
  while ((right - left) > 1);
688
689
114k
  if (diff != 0)
690
111k
  {
691
   /*
692
    * Check the last 1 or 2 elements...
693
    */
694
695
111k
    if ((diff = cups_compare_options(&key, options + left)) <= 0)
696
44.0k
      current = left;
697
67.6k
    else
698
67.6k
    {
699
67.6k
      diff    = cups_compare_options(&key, options + right);
700
67.6k
      current = right;
701
67.6k
    }
702
111k
  }
703
704
 /*
705
  * Return the closest destination and the difference...
706
  */
707
708
114k
  *rdiff = diff;
709
710
114k
  return (current);
711
124k
}