1 /*
2 * Option conflict management routines for CUPS.
3 *
4 * Copyright © 2020-2024 by OpenPrinting.
5 * Copyright 2007-2018 by Apple Inc.
6 * Copyright 1997-2007 by Easy Software Products, all rights reserved.
7 *
8 * Licensed under Apache License v2.0. See the file "LICENSE" for more
9 * information.
10 *
11 * PostScript is a trademark of Adobe Systems, Inc.
12 */
13
14 /*
15 * Include necessary headers...
16 */
17
18 #include "cups-private.h"
19 #include "ppd-private.h"
20 #include "debug-internal.h"
21
22
23 /*
24 * Local constants...
25 */
26
27 enum
28 {
29 _PPD_OPTION_CONSTRAINTS,
30 _PPD_INSTALLABLE_CONSTRAINTS,
31 _PPD_ALL_CONSTRAINTS
32 };
33
34
35 /*
36 * Local functions...
37 */
38
39 static int ppd_is_installable(ppd_group_t *installable,
40 const char *option);
41 static void ppd_load_constraints(ppd_file_t *ppd);
42 static cups_array_t *ppd_test_constraints(ppd_file_t *ppd,
43 const char *option,
44 const char *choice,
45 int num_options,
46 cups_option_t *options,
47 int which);
48
49
50 /*
51 * 'cupsGetConflicts()' - Get a list of conflicting options in a marked PPD.
52 *
53 * This function gets a list of options that would conflict if "option" and
54 * "choice" were marked in the PPD. You would typically call this function
55 * after marking the currently selected options in the PPD in order to
56 * determine whether a new option selection would cause a conflict.
57 *
58 * The number of conflicting options are returned with "options" pointing to
59 * the conflicting options. The returned option array must be freed using
60 * @link cupsFreeOptions@.
61 *
62 * @since CUPS 1.4/macOS 10.6@
63 */
64
65 int /* O - Number of conflicting options */
cupsGetConflicts(ppd_file_t * ppd,const char * option,const char * choice,cups_option_t ** options)66 cupsGetConflicts(
67 ppd_file_t *ppd, /* I - PPD file */
68 const char *option, /* I - Option to test */
69 const char *choice, /* I - Choice to test */
70 cups_option_t **options) /* O - Conflicting options */
71 {
72 int i, /* Looping var */
73 num_options; /* Number of conflicting options */
74 cups_array_t *active; /* Active conflicts */
75 _ppd_cups_uiconsts_t *c; /* Current constraints */
76 _ppd_cups_uiconst_t *cptr; /* Current constraint */
77 ppd_choice_t *marked; /* Marked choice */
78
79
80 /*
81 * Range check input...
82 */
83
84 if (options)
85 *options = NULL;
86
87 if (!ppd || !option || !choice || !options)
88 return (0);
89
90 /*
91 * Test for conflicts...
92 */
93
94 active = ppd_test_constraints(ppd, option, choice, 0, NULL,
95 _PPD_ALL_CONSTRAINTS);
96
97 /*
98 * Loop through all of the UI constraints and add any options that conflict...
99 */
100
101 for (num_options = 0, c = (_ppd_cups_uiconsts_t *)cupsArrayFirst(active);
102 c;
103 c = (_ppd_cups_uiconsts_t *)cupsArrayNext(active))
104 {
105 for (i = c->num_constraints, cptr = c->constraints;
106 i > 0;
107 i --, cptr ++)
108 if (_cups_strcasecmp(cptr->option->keyword, option))
109 {
110 if (cptr->choice)
111 num_options = cupsAddOption(cptr->option->keyword,
112 cptr->choice->choice, num_options,
113 options);
114 else if ((marked = ppdFindMarkedChoice(ppd,
115 cptr->option->keyword)) != NULL)
116 num_options = cupsAddOption(cptr->option->keyword, marked->choice,
117 num_options, options);
118 }
119 }
120
121 cupsArrayDelete(active);
122
123 return (num_options);
124 }
125
126
127 /*
128 * 'cupsResolveConflicts()' - Resolve conflicts in a marked PPD.
129 *
130 * This function attempts to resolve any conflicts in a marked PPD, returning
131 * a list of option changes that are required to resolve them. On input,
132 * "num_options" and "options" contain any pending option changes that have
133 * not yet been marked, while "option" and "choice" contain the most recent
134 * selection which may or may not be in "num_options" or "options".
135 *
136 * On successful return, "num_options" and "options" are updated to contain
137 * "option" and "choice" along with any changes required to resolve conflicts
138 * specified in the PPD file and 1 is returned.
139 *
140 * If option conflicts cannot be resolved, "num_options" and "options" are not
141 * changed and 0 is returned.
142 *
143 * When resolving conflicts, @code cupsResolveConflicts@ does not consider
144 * changes to the current page size (@code media@, @code PageSize@, and
145 * @code PageRegion@) or to the most recent option specified in "option".
146 * Thus, if the only way to resolve a conflict is to change the page size
147 * or the option the user most recently changed, @code cupsResolveConflicts@
148 * will return 0 to indicate it was unable to resolve the conflicts.
149 *
150 * The @code cupsResolveConflicts@ function uses one of two sources of option
151 * constraint information. The preferred constraint information is defined by
152 * @code cupsUIConstraints@ and @code cupsUIResolver@ attributes - in this
153 * case, the PPD file provides constraint resolution actions.
154 *
155 * The backup constraint information is defined by the
156 * @code UIConstraints@ and @code NonUIConstraints@ attributes. These
157 * constraints are resolved algorithmically by first selecting the default
158 * choice for the conflicting option, then iterating over all possible choices
159 * until a non-conflicting option choice is found.
160 *
161 * @since CUPS 1.4/macOS 10.6@
162 */
163
164 int /* O - 1 on success, 0 on failure */
cupsResolveConflicts(ppd_file_t * ppd,const char * option,const char * choice,int * num_options,cups_option_t ** options)165 cupsResolveConflicts(
166 ppd_file_t *ppd, /* I - PPD file */
167 const char *option, /* I - Newly selected option or @code NULL@ for none */
168 const char *choice, /* I - Newly selected choice or @code NULL@ for none */
169 int *num_options, /* IO - Number of additional selected options */
170 cups_option_t **options) /* IO - Additional selected options */
171 {
172 int i, /* Looping var */
173 tries, /* Number of tries */
174 num_newopts; /* Number of new options */
175 cups_option_t *newopts; /* New options */
176 cups_array_t *active = NULL, /* Active constraints */
177 *pass, /* Resolvers for this pass */
178 *resolvers, /* Resolvers we have used */
179 *test; /* Test array for conflicts */
180 _ppd_cups_uiconsts_t *consts; /* Current constraints */
181 _ppd_cups_uiconst_t *constptr; /* Current constraint */
182 ppd_attr_t *resolver; /* Current resolver */
183 const char *resval; /* Pointer into resolver value */
184 char resoption[PPD_MAX_NAME],
185 /* Current resolver option */
186 reschoice[PPD_MAX_NAME],
187 /* Current resolver choice */
188 *resptr, /* Pointer into option/choice */
189 firstpage[255]; /* AP_FIRSTPAGE_Keyword string */
190 const char *value; /* Selected option value */
191 int changed; /* Did we change anything? */
192 ppd_choice_t *marked; /* Marked choice */
193
194
195 /*
196 * Range check input...
197 */
198
199 if (!ppd || !num_options || !options || (option == NULL) != (choice == NULL))
200 return (0);
201
202 /*
203 * Build a shadow option array...
204 */
205
206 num_newopts = 0;
207 newopts = NULL;
208
209 for (i = 0; i < *num_options; i ++)
210 num_newopts = cupsAddOption((*options)[i].name, (*options)[i].value,
211 num_newopts, &newopts);
212 if (option && _cups_strcasecmp(option, "Collate"))
213 num_newopts = cupsAddOption(option, choice, num_newopts, &newopts);
214
215 /*
216 * Loop until we have no conflicts...
217 */
218
219 cupsArraySave(ppd->sorted_attrs);
220
221 resolvers = NULL;
222 pass = cupsArrayNew((cups_array_func_t)_cups_strcasecmp, NULL);
223 tries = 0;
224
225 while (tries < 100 &&
226 (active = ppd_test_constraints(ppd, NULL, NULL, num_newopts, newopts,
227 _PPD_ALL_CONSTRAINTS)) != NULL)
228 {
229 tries ++;
230
231 if (!resolvers)
232 resolvers = cupsArrayNew((cups_array_func_t)_cups_strcasecmp, NULL);
233
234 for (consts = (_ppd_cups_uiconsts_t *)cupsArrayFirst(active), changed = 0;
235 consts;
236 consts = (_ppd_cups_uiconsts_t *)cupsArrayNext(active))
237 {
238 if (consts->resolver[0])
239 {
240 /*
241 * Look up the resolver...
242 */
243
244 if (cupsArrayFind(pass, consts->resolver))
245 continue; /* Already applied this resolver... */
246
247 if (cupsArrayFind(resolvers, consts->resolver))
248 {
249 /*
250 * Resolver loop!
251 */
252
253 DEBUG_printf(("1cupsResolveConflicts: Resolver loop with %s!",
254 consts->resolver));
255 goto error;
256 }
257
258 if ((resolver = ppdFindAttr(ppd, "cupsUIResolver",
259 consts->resolver)) == NULL)
260 {
261 DEBUG_printf(("1cupsResolveConflicts: Resolver %s not found!",
262 consts->resolver));
263 goto error;
264 }
265
266 if (!resolver->value)
267 {
268 DEBUG_printf(("1cupsResolveConflicts: Resolver %s has no value!",
269 consts->resolver));
270 goto error;
271 }
272
273 /*
274 * Add the options from the resolver...
275 */
276
277 cupsArrayAdd(pass, consts->resolver);
278 cupsArrayAdd(resolvers, consts->resolver);
279
280 for (resval = resolver->value; *resval && !changed;)
281 {
282 while (_cups_isspace(*resval))
283 resval ++;
284
285 if (*resval != '*')
286 break;
287
288 for (resval ++, resptr = resoption;
289 *resval && !_cups_isspace(*resval);
290 resval ++)
291 if (resptr < (resoption + sizeof(resoption) - 1))
292 *resptr++ = *resval;
293
294 *resptr = '\0';
295
296 while (_cups_isspace(*resval))
297 resval ++;
298
299 for (resptr = reschoice;
300 *resval && !_cups_isspace(*resval);
301 resval ++)
302 if (resptr < (reschoice + sizeof(reschoice) - 1))
303 *resptr++ = *resval;
304
305 *resptr = '\0';
306
307 if (!resoption[0] || !reschoice[0])
308 break;
309
310 /*
311 * Is this the option we are changing?
312 */
313
314 snprintf(firstpage, sizeof(firstpage), "AP_FIRSTPAGE_%s", resoption);
315
316 if (option &&
317 (!_cups_strcasecmp(resoption, option) ||
318 !_cups_strcasecmp(firstpage, option) ||
319 (!_cups_strcasecmp(option, "PageSize") &&
320 !_cups_strcasecmp(resoption, "PageRegion")) ||
321 (!_cups_strcasecmp(option, "AP_FIRSTPAGE_PageSize") &&
322 !_cups_strcasecmp(resoption, "PageSize")) ||
323 (!_cups_strcasecmp(option, "AP_FIRSTPAGE_PageSize") &&
324 !_cups_strcasecmp(resoption, "PageRegion")) ||
325 (!_cups_strcasecmp(option, "PageRegion") &&
326 !_cups_strcasecmp(resoption, "PageSize")) ||
327 (!_cups_strcasecmp(option, "AP_FIRSTPAGE_PageRegion") &&
328 !_cups_strcasecmp(resoption, "PageSize")) ||
329 (!_cups_strcasecmp(option, "AP_FIRSTPAGE_PageRegion") &&
330 !_cups_strcasecmp(resoption, "PageRegion"))))
331 continue;
332
333 /*
334 * Try this choice...
335 */
336
337 if ((test = ppd_test_constraints(ppd, resoption, reschoice,
338 num_newopts, newopts,
339 _PPD_ALL_CONSTRAINTS)) == NULL)
340 {
341 /*
342 * That worked...
343 */
344
345 changed = 1;
346 }
347 else
348 cupsArrayDelete(test);
349
350 /*
351 * Add the option/choice from the resolver regardless of whether it
352 * worked; this makes sure that we can cascade several changes to
353 * make things resolve...
354 */
355
356 num_newopts = cupsAddOption(resoption, reschoice, num_newopts,
357 &newopts);
358 }
359 }
360 else
361 {
362 /*
363 * Try resolving by choosing the default values for non-installable
364 * options, then by iterating through the possible choices...
365 */
366
367 int j; /* Looping var */
368 ppd_choice_t *cptr; /* Current choice */
369 ppd_size_t *size; /* Current page size */
370
371
372 for (i = consts->num_constraints, constptr = consts->constraints;
373 i > 0 && !changed;
374 i --, constptr ++)
375 {
376 /*
377 * Can't resolve by changing an installable option...
378 */
379
380 if (constptr->installable)
381 continue;
382
383 /*
384 * Is this the option we are changing?
385 */
386
387 if (option &&
388 (!_cups_strcasecmp(constptr->option->keyword, option) ||
389 (!_cups_strcasecmp(option, "PageSize") &&
390 !_cups_strcasecmp(constptr->option->keyword, "PageRegion")) ||
391 (!_cups_strcasecmp(option, "PageRegion") &&
392 !_cups_strcasecmp(constptr->option->keyword, "PageSize"))))
393 continue;
394
395 /*
396 * Get the current option choice...
397 */
398
399 if ((value = cupsGetOption(constptr->option->keyword, num_newopts,
400 newopts)) == NULL)
401 {
402 if (!_cups_strcasecmp(constptr->option->keyword, "PageSize") ||
403 !_cups_strcasecmp(constptr->option->keyword, "PageRegion"))
404 {
405 if ((value = cupsGetOption("PageSize", num_newopts,
406 newopts)) == NULL)
407 value = cupsGetOption("PageRegion", num_newopts, newopts);
408
409 if (!value)
410 {
411 if ((size = ppdPageSize(ppd, NULL)) != NULL)
412 value = size->name;
413 else
414 value = "";
415 }
416 }
417 else
418 {
419 marked = ppdFindMarkedChoice(ppd, constptr->option->keyword);
420 value = marked ? marked->choice : "";
421 }
422 }
423
424 if (!_cups_strncasecmp(value, "Custom.", 7))
425 value = "Custom";
426
427 /*
428 * Try the default choice...
429 */
430
431 test = NULL;
432
433 if (_cups_strcasecmp(value, constptr->option->defchoice) &&
434 (test = ppd_test_constraints(ppd, constptr->option->keyword,
435 constptr->option->defchoice,
436 num_newopts, newopts,
437 _PPD_OPTION_CONSTRAINTS)) == NULL)
438 {
439 /*
440 * That worked...
441 */
442
443 num_newopts = cupsAddOption(constptr->option->keyword,
444 constptr->option->defchoice,
445 num_newopts, &newopts);
446 changed = 1;
447 }
448 else
449 {
450 /*
451 * Try each choice instead...
452 */
453
454 for (j = constptr->option->num_choices,
455 cptr = constptr->option->choices;
456 j > 0;
457 j --, cptr ++)
458 {
459 cupsArrayDelete(test);
460 test = NULL;
461
462 if (_cups_strcasecmp(value, cptr->choice) &&
463 _cups_strcasecmp(constptr->option->defchoice, cptr->choice) &&
464 _cups_strcasecmp("Custom", cptr->choice) &&
465 (test = ppd_test_constraints(ppd, constptr->option->keyword,
466 cptr->choice, num_newopts,
467 newopts,
468 _PPD_OPTION_CONSTRAINTS)) == NULL)
469 {
470 /*
471 * This choice works...
472 */
473
474 num_newopts = cupsAddOption(constptr->option->keyword,
475 cptr->choice, num_newopts,
476 &newopts);
477 changed = 1;
478 break;
479 }
480 }
481
482 cupsArrayDelete(test);
483 }
484 }
485 }
486 }
487
488 if (!changed)
489 {
490 DEBUG_puts("1cupsResolveConflicts: Unable to automatically resolve "
491 "constraint!");
492 goto error;
493 }
494
495 cupsArrayClear(pass);
496 cupsArrayDelete(active);
497 active = NULL;
498 }
499
500 if (tries >= 100)
501 goto error;
502
503 /*
504 * Free the caller's option array...
505 */
506
507 cupsFreeOptions(*num_options, *options);
508
509 /*
510 * If Collate is the option we are testing, add it here. Otherwise, remove
511 * any Collate option from the resolve list since the filters automatically
512 * handle manual collation...
513 */
514
515 if (option && !_cups_strcasecmp(option, "Collate"))
516 num_newopts = cupsAddOption(option, choice, num_newopts, &newopts);
517 else
518 num_newopts = cupsRemoveOption("Collate", num_newopts, &newopts);
519
520 /*
521 * Return the new list of options to the caller...
522 */
523
524 *num_options = num_newopts;
525 *options = newopts;
526
527 cupsArrayDelete(pass);
528 cupsArrayDelete(resolvers);
529
530 cupsArrayRestore(ppd->sorted_attrs);
531
532 DEBUG_printf(("1cupsResolveConflicts: Returning %d options:", num_newopts));
533 #ifdef DEBUG
534 for (i = 0; i < num_newopts; i ++)
535 DEBUG_printf(("1cupsResolveConflicts: options[%d]: %s=%s", i,
536 newopts[i].name, newopts[i].value));
537 #endif /* DEBUG */
538
539 return (1);
540
541 /*
542 * If we get here, we failed to resolve...
543 */
544
545 error:
546
547 cupsFreeOptions(num_newopts, newopts);
548
549 cupsArrayDelete(active);
550 cupsArrayDelete(pass);
551 cupsArrayDelete(resolvers);
552
553 cupsArrayRestore(ppd->sorted_attrs);
554
555 DEBUG_puts("1cupsResolveConflicts: Unable to resolve conflicts!");
556
557 return (0);
558 }
559
560
561 /*
562 * 'ppdConflicts()' - Check to see if there are any conflicts among the
563 * marked option choices.
564 *
565 * The returned value is the same as returned by @link ppdMarkOption@.
566 */
567
568 int /* O - Number of conflicts found */
ppdConflicts(ppd_file_t * ppd)569 ppdConflicts(ppd_file_t *ppd) /* I - PPD to check */
570 {
571 int i, /* Looping variable */
572 conflicts; /* Number of conflicts */
573 cups_array_t *active; /* Active conflicts */
574 _ppd_cups_uiconsts_t *c; /* Current constraints */
575 _ppd_cups_uiconst_t *cptr; /* Current constraint */
576 ppd_option_t *o; /* Current option */
577
578
579 if (!ppd)
580 return (0);
581
582 /*
583 * Clear all conflicts...
584 */
585
586 cupsArraySave(ppd->options);
587
588 for (o = ppdFirstOption(ppd); o; o = ppdNextOption(ppd))
589 o->conflicted = 0;
590
591 cupsArrayRestore(ppd->options);
592
593 /*
594 * Test for conflicts...
595 */
596
597 active = ppd_test_constraints(ppd, NULL, NULL, 0, NULL,
598 _PPD_ALL_CONSTRAINTS);
599 conflicts = cupsArrayCount(active);
600
601 /*
602 * Loop through all of the UI constraints and flag any options
603 * that conflict...
604 */
605
606 for (c = (_ppd_cups_uiconsts_t *)cupsArrayFirst(active);
607 c;
608 c = (_ppd_cups_uiconsts_t *)cupsArrayNext(active))
609 {
610 for (i = c->num_constraints, cptr = c->constraints;
611 i > 0;
612 i --, cptr ++)
613 cptr->option->conflicted = 1;
614 }
615
616 cupsArrayDelete(active);
617
618 /*
619 * Return the number of conflicts found...
620 */
621
622 return (conflicts);
623 }
624
625
626 /*
627 * 'ppdInstallableConflict()' - Test whether an option choice conflicts with
628 * an installable option.
629 *
630 * This function tests whether a particular option choice is available based
631 * on constraints against options in the "InstallableOptions" group.
632 *
633 * @since CUPS 1.4/macOS 10.6@
634 */
635
636 int /* O - 1 if conflicting, 0 if not conflicting */
ppdInstallableConflict(ppd_file_t * ppd,const char * option,const char * choice)637 ppdInstallableConflict(
638 ppd_file_t *ppd, /* I - PPD file */
639 const char *option, /* I - Option */
640 const char *choice) /* I - Choice */
641 {
642 cups_array_t *active; /* Active conflicts */
643
644
645 DEBUG_printf(("2ppdInstallableConflict(ppd=%p, option=\"%s\", choice=\"%s\")",
646 ppd, option, choice));
647
648 /*
649 * Range check input...
650 */
651
652 if (!ppd || !option || !choice)
653 return (0);
654
655 /*
656 * Test constraints using the new option...
657 */
658
659 active = ppd_test_constraints(ppd, option, choice, 0, NULL,
660 _PPD_INSTALLABLE_CONSTRAINTS);
661
662 cupsArrayDelete(active);
663
664 return (active != NULL);
665 }
666
667
668 /*
669 * 'ppd_is_installable()' - Determine whether an option is in the
670 * InstallableOptions group.
671 */
672
673 static int /* O - 1 if installable, 0 if normal */
ppd_is_installable(ppd_group_t * installable,const char * name)674 ppd_is_installable(
675 ppd_group_t *installable, /* I - InstallableOptions group */
676 const char *name) /* I - Option name */
677 {
678 if (installable)
679 {
680 int i; /* Looping var */
681 ppd_option_t *option; /* Current option */
682
683
684 for (i = installable->num_options, option = installable->options;
685 i > 0;
686 i --, option ++)
687 if (!_cups_strcasecmp(option->keyword, name))
688 return (1);
689 }
690
691 return (0);
692 }
693
694
695 /*
696 * 'ppd_load_constraints()' - Load constraints from a PPD file.
697 */
698
699 static void
ppd_load_constraints(ppd_file_t * ppd)700 ppd_load_constraints(ppd_file_t *ppd) /* I - PPD file */
701 {
702 int i; /* Looping var */
703 ppd_const_t *oldconst; /* Current UIConstraints data */
704 ppd_attr_t *constattr; /* Current cupsUIConstraints attribute */
705 _ppd_cups_uiconsts_t *consts; /* Current cupsUIConstraints data */
706 _ppd_cups_uiconst_t *constptr; /* Current constraint */
707 ppd_group_t *installable; /* Installable options group */
708 const char *vptr; /* Pointer into constraint value */
709 char option[PPD_MAX_NAME], /* Option name/MainKeyword */
710 choice[PPD_MAX_NAME], /* Choice/OptionKeyword */
711 *ptr; /* Pointer into option or choice */
712
713
714 DEBUG_printf(("7ppd_load_constraints(ppd=%p)", ppd));
715
716 /*
717 * Create an array to hold the constraint data...
718 */
719
720 ppd->cups_uiconstraints = cupsArrayNew(NULL, NULL);
721
722 /*
723 * Find the installable options group if it exists...
724 */
725
726 for (i = ppd->num_groups, installable = ppd->groups;
727 i > 0;
728 i --, installable ++)
729 if (!_cups_strcasecmp(installable->name, "InstallableOptions"))
730 break;
731
732 if (i <= 0)
733 installable = NULL;
734
735 /*
736 * Load old-style [Non]UIConstraints data...
737 */
738
739 for (i = ppd->num_consts, oldconst = ppd->consts; i > 0; i --, oldconst ++)
740 {
741 /*
742 * Weed out nearby duplicates, since the PPD spec requires that you
743 * define both "*Foo foo *Bar bar" and "*Bar bar *Foo foo"...
744 */
745
746 if (i > 1 &&
747 !_cups_strcasecmp(oldconst[0].option1, oldconst[1].option2) &&
748 !_cups_strcasecmp(oldconst[0].choice1, oldconst[1].choice2) &&
749 !_cups_strcasecmp(oldconst[0].option2, oldconst[1].option1) &&
750 !_cups_strcasecmp(oldconst[0].choice2, oldconst[1].choice1))
751 continue;
752
753 /*
754 * Allocate memory...
755 */
756
757 if ((consts = calloc(1, sizeof(_ppd_cups_uiconsts_t))) == NULL)
758 {
759 DEBUG_puts("8ppd_load_constraints: Unable to allocate memory for "
760 "UIConstraints!");
761 return;
762 }
763
764 if ((constptr = calloc(2, sizeof(_ppd_cups_uiconst_t))) == NULL)
765 {
766 free(consts);
767 DEBUG_puts("8ppd_load_constraints: Unable to allocate memory for "
768 "UIConstraints!");
769 return;
770 }
771
772 /*
773 * Fill in the information...
774 */
775
776 consts->num_constraints = 2;
777 consts->constraints = constptr;
778
779 if (!_cups_strncasecmp(oldconst->option1, "Custom", 6) &&
780 !_cups_strcasecmp(oldconst->choice1, "True"))
781 {
782 constptr[0].option = ppdFindOption(ppd, oldconst->option1 + 6);
783 constptr[0].choice = ppdFindChoice(constptr[0].option, "Custom");
784 constptr[0].installable = 0;
785 }
786 else
787 {
788 constptr[0].option = ppdFindOption(ppd, oldconst->option1);
789 constptr[0].choice = ppdFindChoice(constptr[0].option,
790 oldconst->choice1);
791 constptr[0].installable = ppd_is_installable(installable,
792 oldconst->option1);
793 }
794
795 if (!constptr[0].option || (!constptr[0].choice && oldconst->choice1[0]))
796 {
797 DEBUG_printf(("8ppd_load_constraints: Unknown option *%s %s!",
798 oldconst->option1, oldconst->choice1));
799 free(consts->constraints);
800 free(consts);
801 continue;
802 }
803
804 if (!_cups_strncasecmp(oldconst->option2, "Custom", 6) &&
805 !_cups_strcasecmp(oldconst->choice2, "True"))
806 {
807 constptr[1].option = ppdFindOption(ppd, oldconst->option2 + 6);
808 constptr[1].choice = ppdFindChoice(constptr[1].option, "Custom");
809 constptr[1].installable = 0;
810 }
811 else
812 {
813 constptr[1].option = ppdFindOption(ppd, oldconst->option2);
814 constptr[1].choice = ppdFindChoice(constptr[1].option,
815 oldconst->choice2);
816 constptr[1].installable = ppd_is_installable(installable,
817 oldconst->option2);
818 }
819
820 if (!constptr[1].option || (!constptr[1].choice && oldconst->choice2[0]))
821 {
822 DEBUG_printf(("8ppd_load_constraints: Unknown option *%s %s!",
823 oldconst->option2, oldconst->choice2));
824 free(consts->constraints);
825 free(consts);
826 continue;
827 }
828
829 consts->installable = constptr[0].installable || constptr[1].installable;
830
831 /*
832 * Add it to the constraints array...
833 */
834
835 cupsArrayAdd(ppd->cups_uiconstraints, consts);
836 }
837
838 /*
839 * Then load new-style constraints...
840 */
841
842 for (constattr = ppdFindAttr(ppd, "cupsUIConstraints", NULL);
843 constattr;
844 constattr = ppdFindNextAttr(ppd, "cupsUIConstraints", NULL))
845 {
846 if (!constattr->value)
847 {
848 DEBUG_puts("8ppd_load_constraints: Bad cupsUIConstraints value!");
849 continue;
850 }
851
852 for (i = 0, vptr = strchr(constattr->value, '*');
853 vptr;
854 i ++, vptr = strchr(vptr + 1, '*'));
855
856 if (i == 0)
857 {
858 DEBUG_puts("8ppd_load_constraints: Bad cupsUIConstraints value!");
859 continue;
860 }
861
862 if ((consts = calloc(1, sizeof(_ppd_cups_uiconsts_t))) == NULL)
863 {
864 DEBUG_puts("8ppd_load_constraints: Unable to allocate memory for "
865 "cupsUIConstraints!");
866 return;
867 }
868
869 if ((constptr = calloc((size_t)i, sizeof(_ppd_cups_uiconst_t))) == NULL)
870 {
871 free(consts);
872 DEBUG_puts("8ppd_load_constraints: Unable to allocate memory for "
873 "cupsUIConstraints!");
874 return;
875 }
876
877 consts->num_constraints = i;
878 consts->constraints = constptr;
879
880 strlcpy(consts->resolver, constattr->spec, sizeof(consts->resolver));
881
882 for (i = 0, vptr = strchr(constattr->value, '*');
883 vptr;
884 i ++, vptr = strchr(vptr, '*'), constptr ++)
885 {
886 /*
887 * Extract "*Option Choice" or just "*Option"...
888 */
889
890 for (vptr ++, ptr = option; *vptr && !_cups_isspace(*vptr); vptr ++)
891 if (ptr < (option + sizeof(option) - 1))
892 *ptr++ = *vptr;
893
894 *ptr = '\0';
895
896 while (_cups_isspace(*vptr))
897 vptr ++;
898
899 if (*vptr == '*')
900 choice[0] = '\0';
901 else
902 {
903 for (ptr = choice; *vptr && !_cups_isspace(*vptr); vptr ++)
904 if (ptr < (choice + sizeof(choice) - 1))
905 *ptr++ = *vptr;
906
907 *ptr = '\0';
908 }
909
910 if (!_cups_strncasecmp(option, "Custom", 6) && !_cups_strcasecmp(choice, "True"))
911 {
912 _cups_strcpy(option, option + 6);
913 strlcpy(choice, "Custom", sizeof(choice));
914 }
915
916 constptr->option = ppdFindOption(ppd, option);
917 constptr->choice = ppdFindChoice(constptr->option, choice);
918 constptr->installable = ppd_is_installable(installable, option);
919 consts->installable |= constptr->installable;
920
921 if (!constptr->option || (!constptr->choice && choice[0]))
922 {
923 DEBUG_printf(("8ppd_load_constraints: Unknown option *%s %s!",
924 option, choice));
925 break;
926 }
927 }
928
929 if (!vptr)
930 cupsArrayAdd(ppd->cups_uiconstraints, consts);
931 else
932 {
933 free(consts->constraints);
934 free(consts);
935 }
936 }
937 }
938
939
940 /*
941 * 'ppd_test_constraints()' - See if any constraints are active.
942 */
943
944 static cups_array_t * /* O - Array of active constraints */
ppd_test_constraints(ppd_file_t * ppd,const char * option,const char * choice,int num_options,cups_option_t * options,int which)945 ppd_test_constraints(
946 ppd_file_t *ppd, /* I - PPD file */
947 const char *option, /* I - Current option */
948 const char *choice, /* I - Current choice */
949 int num_options, /* I - Number of additional options */
950 cups_option_t *options, /* I - Additional options */
951 int which) /* I - Which constraints to test */
952 {
953 int i; /* Looping var */
954 _ppd_cups_uiconsts_t *consts; /* Current constraints */
955 _ppd_cups_uiconst_t *constptr; /* Current constraint */
956 ppd_choice_t key, /* Search key */
957 *marked; /* Marked choice */
958 cups_array_t *active = NULL; /* Active constraints */
959 const char *value, /* Current value */
960 *firstvalue; /* AP_FIRSTPAGE_Keyword value */
961 char firstpage[255]; /* AP_FIRSTPAGE_Keyword string */
962
963
964 DEBUG_printf(("7ppd_test_constraints(ppd=%p, option=\"%s\", choice=\"%s\", "
965 "num_options=%d, options=%p, which=%d)", ppd, option, choice,
966 num_options, options, which));
967
968 if (!ppd->cups_uiconstraints)
969 ppd_load_constraints(ppd);
970
971 DEBUG_printf(("9ppd_test_constraints: %d constraints!",
972 cupsArrayCount(ppd->cups_uiconstraints)));
973
974 cupsArraySave(ppd->marked);
975
976 for (consts = (_ppd_cups_uiconsts_t *)cupsArrayFirst(ppd->cups_uiconstraints);
977 consts;
978 consts = (_ppd_cups_uiconsts_t *)cupsArrayNext(ppd->cups_uiconstraints))
979 {
980 DEBUG_printf(("9ppd_test_constraints: installable=%d, resolver=\"%s\", "
981 "num_constraints=%d option1=\"%s\", choice1=\"%s\", "
982 "option2=\"%s\", choice2=\"%s\", ...",
983 consts->installable, consts->resolver, consts->num_constraints,
984 consts->constraints[0].option->keyword,
985 consts->constraints[0].choice ?
986 consts->constraints[0].choice->choice : "",
987 consts->constraints[1].option->keyword,
988 consts->constraints[1].choice ?
989 consts->constraints[1].choice->choice : ""));
990
991 if (consts->installable && which < _PPD_INSTALLABLE_CONSTRAINTS)
992 continue; /* Skip installable option constraint */
993
994 if (!consts->installable && which == _PPD_INSTALLABLE_CONSTRAINTS)
995 continue; /* Skip non-installable option constraint */
996
997 if ((which == _PPD_OPTION_CONSTRAINTS || which == _PPD_INSTALLABLE_CONSTRAINTS) && option)
998 {
999 /*
1000 * Skip constraints that do not involve the current option...
1001 */
1002
1003 for (i = consts->num_constraints, constptr = consts->constraints;
1004 i > 0;
1005 i --, constptr ++)
1006 {
1007 if (!_cups_strcasecmp(constptr->option->keyword, option))
1008 break;
1009
1010 if (!_cups_strncasecmp(option, "AP_FIRSTPAGE_", 13) &&
1011 !_cups_strcasecmp(constptr->option->keyword, option + 13))
1012 break;
1013 }
1014
1015 if (!i)
1016 continue;
1017 }
1018
1019 DEBUG_puts("9ppd_test_constraints: Testing...");
1020
1021 for (i = consts->num_constraints, constptr = consts->constraints;
1022 i > 0;
1023 i --, constptr ++)
1024 {
1025 DEBUG_printf(("9ppd_test_constraints: %s=%s?", constptr->option->keyword,
1026 constptr->choice ? constptr->choice->choice : ""));
1027
1028 if (constptr->choice &&
1029 (!_cups_strcasecmp(constptr->option->keyword, "PageSize") ||
1030 !_cups_strcasecmp(constptr->option->keyword, "PageRegion")))
1031 {
1032 /*
1033 * PageSize and PageRegion are used depending on the selected input slot
1034 * and manual feed mode. Validate against the selected page size instead
1035 * of an individual option...
1036 */
1037
1038 if (option && choice &&
1039 (!_cups_strcasecmp(option, "PageSize") ||
1040 !_cups_strcasecmp(option, "PageRegion")))
1041 {
1042 value = choice;
1043 }
1044 else if ((value = cupsGetOption("PageSize", num_options,
1045 options)) == NULL)
1046 if ((value = cupsGetOption("PageRegion", num_options,
1047 options)) == NULL)
1048 if ((value = cupsGetOption("media", num_options, options)) == NULL)
1049 {
1050 ppd_size_t *size = ppdPageSize(ppd, NULL);
1051
1052 if (size)
1053 value = size->name;
1054 }
1055
1056 if (value && !_cups_strncasecmp(value, "Custom.", 7))
1057 value = "Custom";
1058
1059 if (option && choice &&
1060 (!_cups_strcasecmp(option, "AP_FIRSTPAGE_PageSize") ||
1061 !_cups_strcasecmp(option, "AP_FIRSTPAGE_PageRegion")))
1062 {
1063 firstvalue = choice;
1064 }
1065 else if ((firstvalue = cupsGetOption("AP_FIRSTPAGE_PageSize",
1066 num_options, options)) == NULL)
1067 firstvalue = cupsGetOption("AP_FIRSTPAGE_PageRegion", num_options,
1068 options);
1069
1070 if (firstvalue && !_cups_strncasecmp(firstvalue, "Custom.", 7))
1071 firstvalue = "Custom";
1072
1073 if ((!value || _cups_strcasecmp(value, constptr->choice->choice)) &&
1074 (!firstvalue || _cups_strcasecmp(firstvalue, constptr->choice->choice)))
1075 {
1076 DEBUG_puts("9ppd_test_constraints: NO");
1077 break;
1078 }
1079 }
1080 else if (constptr->choice)
1081 {
1082 /*
1083 * Compare against the constrained choice...
1084 */
1085
1086 if (option && choice && !_cups_strcasecmp(option, constptr->option->keyword))
1087 {
1088 if (!_cups_strncasecmp(choice, "Custom.", 7))
1089 value = "Custom";
1090 else
1091 value = choice;
1092 }
1093 else if ((value = cupsGetOption(constptr->option->keyword, num_options,
1094 options)) != NULL)
1095 {
1096 if (!_cups_strncasecmp(value, "Custom.", 7))
1097 value = "Custom";
1098 }
1099 else if (constptr->choice->marked)
1100 value = constptr->choice->choice;
1101 else
1102 value = NULL;
1103
1104 /*
1105 * Now check AP_FIRSTPAGE_option...
1106 */
1107
1108 snprintf(firstpage, sizeof(firstpage), "AP_FIRSTPAGE_%s",
1109 constptr->option->keyword);
1110
1111 if (option && choice && !_cups_strcasecmp(option, firstpage))
1112 {
1113 if (!_cups_strncasecmp(choice, "Custom.", 7))
1114 firstvalue = "Custom";
1115 else
1116 firstvalue = choice;
1117 }
1118 else if ((firstvalue = cupsGetOption(firstpage, num_options,
1119 options)) != NULL)
1120 {
1121 if (!_cups_strncasecmp(firstvalue, "Custom.", 7))
1122 firstvalue = "Custom";
1123 }
1124 else
1125 firstvalue = NULL;
1126
1127 DEBUG_printf(("9ppd_test_constraints: value=%s, firstvalue=%s", value,
1128 firstvalue));
1129
1130 if ((!value || _cups_strcasecmp(value, constptr->choice->choice)) &&
1131 (!firstvalue || _cups_strcasecmp(firstvalue, constptr->choice->choice)))
1132 {
1133 DEBUG_puts("9ppd_test_constraints: NO");
1134 break;
1135 }
1136 }
1137 else if (option && choice &&
1138 !_cups_strcasecmp(option, constptr->option->keyword))
1139 {
1140 if (!_cups_strcasecmp(choice, "None") || !_cups_strcasecmp(choice, "Off") ||
1141 !_cups_strcasecmp(choice, "False"))
1142 {
1143 DEBUG_puts("9ppd_test_constraints: NO");
1144 break;
1145 }
1146 }
1147 else if ((value = cupsGetOption(constptr->option->keyword, num_options,
1148 options)) != NULL)
1149 {
1150 if (!_cups_strcasecmp(value, "None") || !_cups_strcasecmp(value, "Off") ||
1151 !_cups_strcasecmp(value, "False"))
1152 {
1153 DEBUG_puts("9ppd_test_constraints: NO");
1154 break;
1155 }
1156 }
1157 else
1158 {
1159 key.option = constptr->option;
1160
1161 if ((marked = (ppd_choice_t *)cupsArrayFind(ppd->marked, &key))
1162 == NULL ||
1163 (!_cups_strcasecmp(marked->choice, "None") ||
1164 !_cups_strcasecmp(marked->choice, "Off") ||
1165 !_cups_strcasecmp(marked->choice, "False")))
1166 {
1167 DEBUG_puts("9ppd_test_constraints: NO");
1168 break;
1169 }
1170 }
1171 }
1172
1173 if (i <= 0)
1174 {
1175 if (!active)
1176 active = cupsArrayNew(NULL, NULL);
1177
1178 cupsArrayAdd(active, consts);
1179 DEBUG_puts("9ppd_test_constraints: Added...");
1180 }
1181 }
1182
1183 cupsArrayRestore(ppd->marked);
1184
1185 DEBUG_printf(("8ppd_test_constraints: Found %d active constraints!",
1186 cupsArrayCount(active)));
1187
1188 return (active);
1189 }
1190