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