/src/libcups/cups/options.c
Line | Count | Source (jump to first uncovered line) |
1 | | // |
2 | | // Option routines for CUPS. |
3 | | // |
4 | | // Copyright © 2022-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 |
9 | | // information. |
10 | | // |
11 | | |
12 | | #include "cups-private.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 size_t cups_find_option(const char *name, size_t num_options, cups_option_t *option, int *rdiff); |
21 | | |
22 | | |
23 | | // |
24 | | // 'cupsAddIntegerOption()' - Add an integer option to an option array. |
25 | | // |
26 | | // New option arrays can be initialized simply by passing 0 for the |
27 | | // "num_options" parameter. |
28 | | // |
29 | | |
30 | | size_t // O - Number of options |
31 | | cupsAddIntegerOption( |
32 | | const char *name, // I - Name of option |
33 | | long value, // I - Value of option |
34 | | size_t num_options, // I - Number of options |
35 | | cups_option_t **options) // IO - Pointer to options |
36 | 0 | { |
37 | 0 | char strvalue[32]; // String value |
38 | | |
39 | |
|
40 | 0 | snprintf(strvalue, sizeof(strvalue), "%ld", value); |
41 | |
|
42 | 0 | return (cupsAddOption(name, strvalue, num_options, options)); |
43 | 0 | } |
44 | | |
45 | | |
46 | | // |
47 | | // 'cupsAddOption()' - Add an option to an option array. |
48 | | // |
49 | | // New option arrays can be initialized simply by passing 0 for the |
50 | | // "num_options" parameter. |
51 | | // |
52 | | |
53 | | size_t // O - Number of options |
54 | | cupsAddOption(const char *name, // I - Name of option |
55 | | const char *value, // I - Value of option |
56 | | size_t num_options,// I - Number of options |
57 | | cups_option_t **options) // IO - Pointer to options |
58 | 0 | { |
59 | 0 | cups_option_t *temp; // Pointer to new option |
60 | 0 | size_t insert; // Insertion point |
61 | 0 | int diff; // Result of search |
62 | | |
63 | |
|
64 | 0 | if (!name || !name[0] || !value || !options) |
65 | 0 | return (num_options); |
66 | | |
67 | 0 | if (!_cups_strcasecmp(name, "cupsPrintQuality")) |
68 | 0 | num_options = cupsRemoveOption("print-quality", num_options, options); |
69 | 0 | else if (!_cups_strcasecmp(name, "print-quality")) |
70 | 0 | num_options = cupsRemoveOption("cupsPrintQuality", num_options, options); |
71 | | |
72 | | // Look for an existing option with the same name... |
73 | 0 | if (num_options == 0) |
74 | 0 | { |
75 | 0 | insert = 0; |
76 | 0 | diff = 1; |
77 | 0 | } |
78 | 0 | else |
79 | 0 | { |
80 | 0 | insert = cups_find_option(name, num_options, *options, &diff); |
81 | |
|
82 | 0 | if (diff > 0) |
83 | 0 | insert ++; |
84 | 0 | } |
85 | |
|
86 | 0 | if (diff) |
87 | 0 | { |
88 | | // No matching option name... |
89 | 0 | if ((temp = (cups_option_t *)realloc(num_options ? *options : NULL, sizeof(cups_option_t) * (num_options + 1))) == NULL) |
90 | 0 | return (0); |
91 | | |
92 | 0 | *options = temp; |
93 | |
|
94 | 0 | if (insert < num_options) |
95 | 0 | memmove(temp + insert + 1, temp + insert, (num_options - insert) * sizeof(cups_option_t)); |
96 | |
|
97 | 0 | temp += insert; |
98 | 0 | temp->name = _cupsStrAlloc(name); |
99 | 0 | num_options ++; |
100 | 0 | } |
101 | 0 | else |
102 | 0 | { |
103 | | // Match found; free the old value... |
104 | 0 | temp = *options + insert; |
105 | 0 | _cupsStrFree(temp->value); |
106 | 0 | } |
107 | | |
108 | 0 | temp->value = _cupsStrAlloc(value); |
109 | |
|
110 | 0 | return (num_options); |
111 | 0 | } |
112 | | |
113 | | |
114 | | // |
115 | | // 'cupsFreeOptions()' - Free all memory used by options. |
116 | | // |
117 | | |
118 | | void |
119 | | cupsFreeOptions( |
120 | | size_t num_options, // I - Number of options |
121 | | cups_option_t *options) // I - Pointer to options |
122 | 0 | { |
123 | 0 | size_t i; // Looping var |
124 | | |
125 | |
|
126 | 0 | if (num_options == 0 || !options) |
127 | 0 | return; |
128 | | |
129 | 0 | for (i = 0; i < num_options; i ++) |
130 | 0 | { |
131 | 0 | _cupsStrFree(options[i].name); |
132 | 0 | _cupsStrFree(options[i].value); |
133 | 0 | } |
134 | |
|
135 | 0 | free(options); |
136 | 0 | } |
137 | | |
138 | | |
139 | | // |
140 | | // 'cupsGetIntegerOption()' - Get an integer option value. |
141 | | // |
142 | | // `LONG_MIN` is returned when the option does not exist, is not an integer, or |
143 | | // exceeds the range of values for the `long` type. |
144 | | // |
145 | | |
146 | | long // O - Option value or `LONG_MIN` |
147 | | cupsGetIntegerOption( |
148 | | const char *name, // I - Name of option |
149 | | size_t num_options, // I - Number of options |
150 | | cups_option_t *options) // I - Options |
151 | 0 | { |
152 | 0 | const char *value = cupsGetOption(name, num_options, options); |
153 | | // String value of option |
154 | 0 | char *ptr; // Pointer into string value |
155 | 0 | long lvalue; // Integer value |
156 | | |
157 | |
|
158 | 0 | if (!value || !*value) |
159 | 0 | return (LONG_MIN); |
160 | | |
161 | 0 | lvalue = strtol(value, &ptr, 10); |
162 | |
|
163 | 0 | if (*ptr) |
164 | 0 | return (LONG_MIN); |
165 | 0 | else |
166 | 0 | return (lvalue); |
167 | 0 | } |
168 | | |
169 | | |
170 | | // |
171 | | // 'cupsGetOption()' - Get an option value. |
172 | | // |
173 | | |
174 | | const char * // O - Option value or @code NULL@ |
175 | | cupsGetOption(const char *name, // I - Name of option |
176 | | size_t num_options,// I - Number of options |
177 | | cups_option_t *options) // I - Options |
178 | 0 | { |
179 | 0 | int diff; // Result of comparison |
180 | 0 | size_t match; // Matching index |
181 | | |
182 | |
|
183 | 0 | if (!name || num_options == 0 || !options) |
184 | 0 | return (NULL); |
185 | | |
186 | 0 | match = cups_find_option(name, num_options, options, &diff); |
187 | |
|
188 | 0 | if (!diff) |
189 | 0 | return (options[match].value); |
190 | | |
191 | 0 | return (NULL); |
192 | 0 | } |
193 | | |
194 | | |
195 | | // |
196 | | // 'cupsParseOptions()' - Parse options from a command-line argument. |
197 | | // |
198 | | // This function converts space-delimited name/value pairs according |
199 | | // to the PAPI text option ABNF specification. Collection values |
200 | | // ("name={a=... b=... c=...}") are stored with the curley brackets |
201 | | // intact - use @code cupsParseOptions@ on the value to extract the |
202 | | // collection attributes. |
203 | | // |
204 | | // The "end" argument, if not `NULL`, receives a pointer to the end of the |
205 | | // options. |
206 | | // |
207 | | |
208 | | size_t // O - Number of options found |
209 | | cupsParseOptions( |
210 | | const char *arg, // I - Argument to parse |
211 | | const char **end, // O - Pointer to end of options or `NULL` for "don't care" |
212 | | size_t num_options, // I - Number of options |
213 | | cups_option_t **options) // O - Options found |
214 | 0 | { |
215 | 0 | char *copyarg, // Copy of input string |
216 | 0 | *ptr, // Pointer into string |
217 | 0 | *name, // Pointer to name |
218 | 0 | *value, // Pointer to value |
219 | 0 | sep, // Separator character |
220 | 0 | quote; // Quote character |
221 | | |
222 | | |
223 | | // Range check input... |
224 | 0 | if (end) |
225 | 0 | *end = NULL; |
226 | |
|
227 | 0 | if (!arg) |
228 | 0 | return (num_options); |
229 | | |
230 | 0 | if (!options) |
231 | 0 | return (0); |
232 | | |
233 | | // Make a copy of the argument string and then divide it up... |
234 | 0 | if ((copyarg = strdup(arg)) == NULL) |
235 | 0 | { |
236 | 0 | DEBUG_puts("1cupsParseOptions: Unable to copy arg string"); |
237 | 0 | return (num_options); |
238 | 0 | } |
239 | | |
240 | 0 | if (*copyarg == '{') |
241 | 0 | ptr = copyarg + 1; |
242 | 0 | else |
243 | 0 | ptr = copyarg; |
244 | | |
245 | | // Skip leading spaces... |
246 | 0 | while (_cups_isspace(*ptr)) |
247 | 0 | ptr ++; |
248 | | |
249 | | // Loop through the string... |
250 | 0 | while (*ptr != '\0') |
251 | 0 | { |
252 | | // Get the name up to a SPACE, =, or end-of-string... |
253 | 0 | name = ptr; |
254 | 0 | while (!strchr("\f\n\r\t\v =", *ptr) && *ptr) |
255 | 0 | ptr ++; |
256 | | |
257 | | // Avoid an empty name... |
258 | 0 | if (ptr == name) |
259 | 0 | break; |
260 | | |
261 | | // End after the closing brace... |
262 | 0 | if (*ptr == '}' && *copyarg == '{') |
263 | 0 | { |
264 | 0 | *ptr++ = '\0'; |
265 | 0 | break; |
266 | 0 | } |
267 | | |
268 | | // Skip trailing spaces... |
269 | 0 | while (_cups_isspace(*ptr)) |
270 | 0 | *ptr++ = '\0'; |
271 | |
|
272 | 0 | if ((sep = *ptr) == '=') |
273 | 0 | *ptr++ = '\0'; |
274 | |
|
275 | 0 | if (sep != '=') |
276 | 0 | { |
277 | | // Boolean option... |
278 | 0 | if (!_cups_strncasecmp(name, "no", 2)) |
279 | 0 | num_options = cupsAddOption(name + 2, "false", num_options, options); |
280 | 0 | else |
281 | 0 | num_options = cupsAddOption(name, "true", num_options, options); |
282 | |
|
283 | 0 | continue; |
284 | 0 | } |
285 | | |
286 | | // Remove = and parse the value... |
287 | 0 | value = ptr; |
288 | |
|
289 | 0 | while (*ptr && !_cups_isspace(*ptr)) |
290 | 0 | { |
291 | 0 | if (*ptr == ',') |
292 | 0 | { |
293 | 0 | ptr ++; |
294 | 0 | } |
295 | 0 | else if (*ptr == '\'' || *ptr == '\"') |
296 | 0 | { |
297 | | // Quoted string constant... |
298 | 0 | quote = *ptr; |
299 | 0 | _cups_strcpy(ptr, ptr + 1); |
300 | |
|
301 | 0 | while (*ptr != quote && *ptr) |
302 | 0 | { |
303 | 0 | if (*ptr == '\\' && ptr[1]) |
304 | 0 | _cups_strcpy(ptr, ptr + 1); |
305 | |
|
306 | 0 | ptr ++; |
307 | 0 | } |
308 | |
|
309 | 0 | if (*ptr) |
310 | 0 | _cups_strcpy(ptr, ptr + 1); |
311 | 0 | } |
312 | 0 | else if (*ptr == '{') |
313 | 0 | { |
314 | | // Collection value... |
315 | 0 | int depth; // Nesting depth for braces |
316 | |
|
317 | 0 | for (depth = 0; *ptr; ptr ++) |
318 | 0 | { |
319 | 0 | if (*ptr == '{') |
320 | 0 | { |
321 | 0 | depth ++; |
322 | 0 | } |
323 | 0 | else if (*ptr == '}') |
324 | 0 | { |
325 | 0 | depth --; |
326 | 0 | if (!depth) |
327 | 0 | { |
328 | 0 | ptr ++; |
329 | 0 | break; |
330 | 0 | } |
331 | 0 | } |
332 | 0 | else if (*ptr == '\\' && ptr[1]) |
333 | 0 | { |
334 | 0 | _cups_strcpy(ptr, ptr + 1); |
335 | 0 | } |
336 | 0 | } |
337 | 0 | } |
338 | 0 | else |
339 | 0 | { |
340 | | // Normal space-delimited string... |
341 | 0 | while (*ptr && !_cups_isspace(*ptr)) |
342 | 0 | { |
343 | 0 | if (*ptr == '}' && *copyarg == '{') |
344 | 0 | { |
345 | 0 | *ptr++ = '\0'; |
346 | 0 | break; |
347 | 0 | } |
348 | | |
349 | 0 | if (*ptr == '\\' && ptr[1]) |
350 | 0 | _cups_strcpy(ptr, ptr + 1); |
351 | |
|
352 | 0 | ptr ++; |
353 | 0 | } |
354 | 0 | } |
355 | 0 | } |
356 | |
|
357 | 0 | if (*ptr != '\0') |
358 | 0 | *ptr++ = '\0'; |
359 | | |
360 | | // Skip trailing whitespace... |
361 | 0 | while (_cups_isspace(*ptr)) |
362 | 0 | ptr ++; |
363 | | |
364 | | // Add the string value... |
365 | 0 | num_options = cupsAddOption(name, value, num_options, options); |
366 | 0 | } |
367 | | |
368 | | // Save the progress in the input string... |
369 | 0 | if (end) |
370 | 0 | *end = arg + (ptr - copyarg); |
371 | | |
372 | | // Free the copy of the argument we made and return the number of options found. |
373 | 0 | free(copyarg); |
374 | |
|
375 | 0 | return (num_options); |
376 | 0 | } |
377 | | |
378 | | |
379 | | // |
380 | | // 'cupsRemoveOption()' - Remove an option from an option array. |
381 | | // |
382 | | |
383 | | size_t // O - New number of options |
384 | | cupsRemoveOption( |
385 | | const char *name, // I - Option name |
386 | | size_t num_options, // I - Current number of options |
387 | | cups_option_t **options) // IO - Options |
388 | 0 | { |
389 | 0 | size_t i; // Looping var |
390 | 0 | cups_option_t *option; // Current option |
391 | | |
392 | | |
393 | | // Range check input... |
394 | 0 | if (!name || num_options == 0 || !options) |
395 | 0 | return (num_options); |
396 | | |
397 | | // Loop for the option... |
398 | 0 | for (i = num_options, option = *options; i > 0; i --, option ++) |
399 | 0 | { |
400 | 0 | if (!_cups_strcasecmp(name, option->name)) |
401 | 0 | break; |
402 | 0 | } |
403 | |
|
404 | 0 | if (i) |
405 | 0 | { |
406 | | // Remove this option from the array... |
407 | 0 | num_options --; |
408 | 0 | i --; |
409 | |
|
410 | 0 | _cupsStrFree(option->name); |
411 | 0 | _cupsStrFree(option->value); |
412 | |
|
413 | 0 | if (i > 0) |
414 | 0 | memmove(option, option + 1, i * sizeof(cups_option_t)); |
415 | 0 | } |
416 | | |
417 | | // Return the new number of options... |
418 | 0 | return (num_options); |
419 | 0 | } |
420 | | |
421 | | |
422 | | // |
423 | | // 'cups_compare_options()' - Compare two options. |
424 | | // |
425 | | |
426 | | static int // O - Result of comparison |
427 | | cups_compare_options(cups_option_t *a, // I - First option |
428 | | cups_option_t *b) // I - Second option |
429 | 0 | { |
430 | 0 | return (_cups_strcasecmp(a->name, b->name)); |
431 | 0 | } |
432 | | |
433 | | |
434 | | // |
435 | | // 'cups_find_option()' - Find an option using a binary search. |
436 | | // |
437 | | |
438 | | static size_t // O - Index of match |
439 | | cups_find_option( |
440 | | const char *name, // I - Option name |
441 | | size_t num_options, // I - Number of options |
442 | | cups_option_t *options, // I - Options |
443 | | int *rdiff) // O - Difference of match |
444 | 0 | { |
445 | 0 | size_t left, // Low mark for binary search |
446 | 0 | right, // High mark for binary search |
447 | 0 | current; // Current index |
448 | 0 | int diff; // Result of comparison |
449 | 0 | cups_option_t key; // Search key |
450 | | |
451 | |
|
452 | 0 | key.name = (char *)name; |
453 | | |
454 | | // Start search in the middle... |
455 | 0 | left = 0; |
456 | 0 | right = num_options - 1; |
457 | |
|
458 | 0 | do |
459 | 0 | { |
460 | 0 | current = (left + right) / 2; |
461 | 0 | diff = cups_compare_options(&key, options + current); |
462 | |
|
463 | 0 | if (diff == 0) |
464 | 0 | break; |
465 | 0 | else if (diff < 0) |
466 | 0 | right = current; |
467 | 0 | else |
468 | 0 | left = current; |
469 | 0 | } |
470 | 0 | while ((right - left) > 1); |
471 | | |
472 | 0 | if (diff != 0) |
473 | 0 | { |
474 | | // Check the last 1 or 2 elements... |
475 | 0 | if ((diff = cups_compare_options(&key, options + left)) <= 0) |
476 | 0 | { |
477 | 0 | current = left; |
478 | 0 | } |
479 | 0 | else |
480 | 0 | { |
481 | 0 | diff = cups_compare_options(&key, options + right); |
482 | 0 | current = right; |
483 | 0 | } |
484 | 0 | } |
485 | | |
486 | | // Return the closest destination and the difference... |
487 | 0 | *rdiff = diff; |
488 | |
|
489 | 0 | return (current); |
490 | 0 | } |