• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * PPD localization 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 functions...
25  */
26 
27 static cups_lang_t	*ppd_ll_CC(char *ll_CC, size_t ll_CC_size);
28 
29 
30 /*
31  * 'ppdLocalize()' - Localize the PPD file to the current locale.
32  *
33  * All groups, options, and choices are localized, as are ICC profile
34  * descriptions, printer presets, and custom option parameters.  Each
35  * localized string uses the UTF-8 character encoding.
36  *
37  * @since CUPS 1.2/macOS 10.5@
38  */
39 
40 int					/* O - 0 on success, -1 on error */
ppdLocalize(ppd_file_t * ppd)41 ppdLocalize(ppd_file_t *ppd)		/* I - PPD file */
42 {
43   int		i, j, k;		/* Looping vars */
44   ppd_group_t	*group;			/* Current group */
45   ppd_option_t	*option;		/* Current option */
46   ppd_choice_t	*choice;		/* Current choice */
47   ppd_coption_t	*coption;		/* Current custom option */
48   ppd_cparam_t	*cparam;		/* Current custom parameter */
49   ppd_attr_t	*attr,			/* Current attribute */
50 		*locattr;		/* Localized attribute */
51   char		ckeyword[PPD_MAX_NAME],	/* Custom keyword */
52 		ll_CC[6];		/* Language + country locale */
53 
54 
55  /*
56   * Range check input...
57   */
58 
59   DEBUG_printf(("ppdLocalize(ppd=%p)", ppd));
60 
61   if (!ppd)
62     return (-1);
63 
64  /*
65   * Get the default language...
66   */
67 
68   ppd_ll_CC(ll_CC, sizeof(ll_CC));
69 
70  /*
71   * Now lookup all of the groups, options, choices, etc.
72   */
73 
74   for (i = ppd->num_groups, group = ppd->groups; i > 0; i --, group ++)
75   {
76     if ((locattr = _ppdLocalizedAttr(ppd, "Translation", group->name,
77                                      ll_CC)) != NULL)
78       strlcpy(group->text, locattr->text, sizeof(group->text));
79 
80     for (j = group->num_options, option = group->options; j > 0; j --, option ++)
81     {
82       if ((locattr = _ppdLocalizedAttr(ppd, "Translation", option->keyword,
83                                        ll_CC)) != NULL)
84 	strlcpy(option->text, locattr->text, sizeof(option->text));
85 
86       for (k = option->num_choices, choice = option->choices;
87            k > 0;
88 	   k --, choice ++)
89       {
90         if (strcmp(choice->choice, "Custom") ||
91 	    !ppdFindCustomOption(ppd, option->keyword))
92 	  locattr = _ppdLocalizedAttr(ppd, option->keyword, choice->choice,
93 	                              ll_CC);
94 	else
95 	{
96 	  snprintf(ckeyword, sizeof(ckeyword), "Custom%s", option->keyword);
97 
98 	  locattr = _ppdLocalizedAttr(ppd, ckeyword, "True", ll_CC);
99 	}
100 
101         if (locattr)
102 	  strlcpy(choice->text, locattr->text, sizeof(choice->text));
103       }
104     }
105   }
106 
107  /*
108   * Translate any custom parameters...
109   */
110 
111   for (coption = (ppd_coption_t *)cupsArrayFirst(ppd->coptions);
112        coption;
113        coption = (ppd_coption_t *)cupsArrayNext(ppd->coptions))
114   {
115     for (cparam = (ppd_cparam_t *)cupsArrayFirst(coption->params);
116 	 cparam;
117 	 cparam = (ppd_cparam_t *)cupsArrayNext(coption->params))
118     {
119       snprintf(ckeyword, sizeof(ckeyword), "ParamCustom%s", coption->keyword);
120 
121       if ((locattr = _ppdLocalizedAttr(ppd, ckeyword, cparam->name,
122                                        ll_CC)) != NULL)
123         strlcpy(cparam->text, locattr->text, sizeof(cparam->text));
124     }
125   }
126 
127  /*
128   * Translate ICC profile names...
129   */
130 
131   if ((attr = ppdFindAttr(ppd, "APCustomColorMatchingName", NULL)) != NULL)
132   {
133     if ((locattr = _ppdLocalizedAttr(ppd, "APCustomColorMatchingName",
134                                      attr->spec, ll_CC)) != NULL)
135       strlcpy(attr->text, locattr->text, sizeof(attr->text));
136   }
137 
138   for (attr = ppdFindAttr(ppd, "cupsICCProfile", NULL);
139        attr;
140        attr = ppdFindNextAttr(ppd, "cupsICCProfile", NULL))
141   {
142     cupsArraySave(ppd->sorted_attrs);
143 
144     if ((locattr = _ppdLocalizedAttr(ppd, "cupsICCProfile", attr->spec,
145                                      ll_CC)) != NULL)
146       strlcpy(attr->text, locattr->text, sizeof(attr->text));
147 
148     cupsArrayRestore(ppd->sorted_attrs);
149   }
150 
151  /*
152   * Translate printer presets...
153   */
154 
155   for (attr = ppdFindAttr(ppd, "APPrinterPreset", NULL);
156        attr;
157        attr = ppdFindNextAttr(ppd, "APPrinterPreset", NULL))
158   {
159     cupsArraySave(ppd->sorted_attrs);
160 
161     if ((locattr = _ppdLocalizedAttr(ppd, "APPrinterPreset", attr->spec,
162                                      ll_CC)) != NULL)
163       strlcpy(attr->text, locattr->text, sizeof(attr->text));
164 
165     cupsArrayRestore(ppd->sorted_attrs);
166   }
167 
168   return (0);
169 }
170 
171 
172 /*
173  * 'ppdLocalizeAttr()' - Localize an attribute.
174  *
175  * This function uses the current locale to find the localized attribute for
176  * the given main and option keywords.  If no localized version of the
177  * attribute exists for the current locale, the unlocalized version is returned.
178  */
179 
180 ppd_attr_t *				/* O - Localized attribute or @code NULL@ if none exists */
ppdLocalizeAttr(ppd_file_t * ppd,const char * keyword,const char * spec)181 ppdLocalizeAttr(ppd_file_t *ppd,	/* I - PPD file */
182 		const char *keyword,	/* I - Main keyword */
183 		const char *spec)	/* I - Option keyword or @code NULL@ for none */
184 {
185   ppd_attr_t	*locattr;		/* Localized attribute */
186   char		ll_CC[6];		/* Language + country locale */
187 
188 
189  /*
190   * Get the default language...
191   */
192 
193   ppd_ll_CC(ll_CC, sizeof(ll_CC));
194 
195  /*
196   * Find the localized attribute...
197   */
198 
199   if (spec)
200     locattr = _ppdLocalizedAttr(ppd, keyword, spec, ll_CC);
201   else
202     locattr = _ppdLocalizedAttr(ppd, "Translation", keyword, ll_CC);
203 
204   if (!locattr)
205     locattr = ppdFindAttr(ppd, keyword, spec);
206 
207   return (locattr);
208 }
209 
210 
211 /*
212  * 'ppdLocalizeIPPReason()' - Get the localized version of a cupsIPPReason
213  *                            attribute.
214  *
215  * This function uses the current locale to find the corresponding reason
216  * text or URI from the attribute value. If "scheme" is NULL or "text",
217  * the returned value contains human-readable (UTF-8) text from the translation
218  * string or attribute value. Otherwise the corresponding URI is returned.
219  *
220  * If no value of the requested scheme can be found, NULL is returned.
221  *
222  * @since CUPS 1.3/macOS 10.5@
223  */
224 
225 const char *				/* O - Value or NULL if not found */
ppdLocalizeIPPReason(ppd_file_t * ppd,const char * reason,const char * scheme,char * buffer,size_t bufsize)226 ppdLocalizeIPPReason(
227     ppd_file_t *ppd,			/* I - PPD file */
228     const char *reason,			/* I - IPP reason keyword to look up */
229     const char *scheme,			/* I - URI scheme or NULL for text */
230     char       *buffer,			/* I - Value buffer */
231     size_t     bufsize)			/* I - Size of value buffer */
232 {
233   cups_lang_t	*lang;			/* Current language */
234   ppd_attr_t	*locattr;		/* Localized attribute */
235   char		ll_CC[6],		/* Language + country locale */
236 		*bufptr,		/* Pointer into buffer */
237 		*bufend,		/* Pointer to end of buffer */
238 		*valptr;		/* Pointer into value */
239   int		ch;			/* Hex-encoded character */
240   size_t	schemelen;		/* Length of scheme name */
241 
242 
243  /*
244   * Range check input...
245   */
246 
247   if (buffer)
248     *buffer = '\0';
249 
250   if (!ppd || !reason || (scheme && !*scheme) ||
251       !buffer || bufsize < PPD_MAX_TEXT)
252     return (NULL);
253 
254  /*
255   * Get the default language...
256   */
257 
258   lang = ppd_ll_CC(ll_CC, sizeof(ll_CC));
259 
260  /*
261   * Find the localized attribute...
262   */
263 
264   if ((locattr = _ppdLocalizedAttr(ppd, "cupsIPPReason", reason,
265                                    ll_CC)) == NULL)
266     locattr = ppdFindAttr(ppd, "cupsIPPReason", reason);
267 
268   if (!locattr)
269   {
270     if (lang && (!scheme || !strcmp(scheme, "text")) && strcmp(reason, "none"))
271     {
272      /*
273       * Try to localize a standard printer-state-reason keyword...
274       */
275 
276       char	msgid[1024],		/* State message identifier */
277 		*ptr;			/* Pointer to state suffix */
278       const char *message = NULL;	/* Localized message */
279 
280       snprintf(msgid, sizeof(msgid), "printer-state-reasons.%s", reason);
281       if ((ptr = strrchr(msgid, '-')) != NULL && (!strcmp(ptr, "-error") || !strcmp(ptr, "-report") || !strcmp(ptr, "-warning")))
282         *ptr = '\0';
283 
284       message = _cupsLangString(lang, msgid);
285 
286       if (message && strcmp(message, msgid))
287       {
288         strlcpy(buffer, _cupsLangString(lang, message), bufsize);
289 	return (buffer);
290       }
291     }
292 
293     return (NULL);
294   }
295 
296  /*
297   * Now find the value we need...
298   */
299 
300   bufend = buffer + bufsize - 1;
301 
302   if (!scheme || !strcmp(scheme, "text"))
303   {
304    /*
305     * Copy a text value (either the translation text or text:... URIs from
306     * the value...
307     */
308 
309     strlcpy(buffer, locattr->text, bufsize);
310 
311     for (valptr = locattr->value, bufptr = buffer; *valptr && bufptr < bufend;)
312     {
313       if (!strncmp(valptr, "text:", 5))
314       {
315        /*
316         * Decode text: URI and add to the buffer...
317 	*/
318 
319 	valptr += 5;
320 
321         while (*valptr && !_cups_isspace(*valptr) && bufptr < bufend)
322 	{
323 	  if (*valptr == '%' && isxdigit(valptr[1] & 255) &&
324 	      isxdigit(valptr[2] & 255))
325 	  {
326 	   /*
327 	    * Pull a hex-encoded character from the URI...
328 	    */
329 
330             valptr ++;
331 
332 	    if (isdigit(*valptr & 255))
333 	      ch = (*valptr - '0') << 4;
334 	    else
335 	      ch = (tolower(*valptr) - 'a' + 10) << 4;
336 	    valptr ++;
337 
338 	    if (isdigit(*valptr & 255))
339 	      *bufptr++ = (char)(ch | (*valptr - '0'));
340 	    else
341 	      *bufptr++ = (char)(ch | (tolower(*valptr) - 'a' + 10));
342 	    valptr ++;
343 	  }
344 	  else if (*valptr == '+')
345 	  {
346 	    *bufptr++ = ' ';
347 	    valptr ++;
348 	  }
349 	  else
350 	    *bufptr++ = *valptr++;
351         }
352       }
353       else
354       {
355        /*
356         * Skip this URI...
357 	*/
358 
359         while (*valptr && !_cups_isspace(*valptr))
360           valptr++;
361       }
362 
363      /*
364       * Skip whitespace...
365       */
366 
367       while (_cups_isspace(*valptr))
368 	valptr ++;
369     }
370 
371     if (bufptr > buffer)
372       *bufptr = '\0';
373 
374     return (buffer);
375   }
376   else
377   {
378    /*
379     * Copy a URI...
380     */
381 
382     schemelen = strlen(scheme);
383     if (scheme[schemelen - 1] == ':')	/* Force scheme to be just the name */
384       schemelen --;
385 
386     for (valptr = locattr->value, bufptr = buffer; *valptr && bufptr < bufend;)
387     {
388       if ((!strncmp(valptr, scheme, schemelen) && valptr[schemelen] == ':') ||
389           (*valptr == '/' && !strcmp(scheme, "file")))
390       {
391        /*
392         * Copy URI...
393 	*/
394 
395         while (*valptr && !_cups_isspace(*valptr) && bufptr < bufend)
396 	  *bufptr++ = *valptr++;
397 
398 	*bufptr = '\0';
399 
400 	return (buffer);
401       }
402       else
403       {
404        /*
405         * Skip this URI...
406 	*/
407 
408 	while (*valptr && !_cups_isspace(*valptr))
409 	  valptr++;
410       }
411 
412      /*
413       * Skip whitespace...
414       */
415 
416       while (_cups_isspace(*valptr))
417 	valptr ++;
418     }
419 
420     return (NULL);
421   }
422 }
423 
424 
425 /*
426  * 'ppdLocalizeMarkerName()' - Get the localized version of a marker-names
427  *                             attribute value.
428  *
429  * This function uses the current locale to find the corresponding name
430  * text from the attribute value. If no localized text for the requested
431  * name can be found, @code NULL@ is returned.
432  *
433  * @since CUPS 1.4/macOS 10.6@
434  */
435 
436 const char *				/* O - Value or @code NULL@ if not found */
ppdLocalizeMarkerName(ppd_file_t * ppd,const char * name)437 ppdLocalizeMarkerName(
438     ppd_file_t *ppd,			/* I - PPD file */
439     const char *name)			/* I - Marker name to look up */
440 {
441   ppd_attr_t	*locattr;		/* Localized attribute */
442   char		ll_CC[6];		/* Language + country locale */
443 
444 
445  /*
446   * Range check input...
447   */
448 
449   if (!ppd || !name)
450     return (NULL);
451 
452  /*
453   * Get the default language...
454   */
455 
456   ppd_ll_CC(ll_CC, sizeof(ll_CC));
457 
458  /*
459   * Find the localized attribute...
460   */
461 
462   if ((locattr = _ppdLocalizedAttr(ppd, "cupsMarkerName", name,
463                                    ll_CC)) == NULL)
464     locattr = ppdFindAttr(ppd, "cupsMarkerName", name);
465 
466   return (locattr ? locattr->text : NULL);
467 }
468 
469 
470 /*
471  * '_ppdFreeLanguages()' - Free an array of languages from _ppdGetLanguages.
472  */
473 
474 void
_ppdFreeLanguages(cups_array_t * languages)475 _ppdFreeLanguages(
476     cups_array_t *languages)		/* I - Languages array */
477 {
478   char	*language;			/* Current language */
479 
480 
481   for (language = (char *)cupsArrayFirst(languages);
482        language;
483        language = (char *)cupsArrayNext(languages))
484     free(language);
485 
486   cupsArrayDelete(languages);
487 }
488 
489 
490 /*
491  * '_ppdGetLanguages()' - Get an array of languages from a PPD file.
492  */
493 
494 cups_array_t *				/* O - Languages array */
_ppdGetLanguages(ppd_file_t * ppd)495 _ppdGetLanguages(ppd_file_t *ppd)	/* I - PPD file */
496 {
497   cups_array_t	*languages;		/* Languages array */
498   ppd_attr_t	*attr;			/* cupsLanguages attribute */
499   char		*value,			/* Copy of attribute value */
500 		*start,			/* Start of current language */
501 		*ptr;			/* Pointer into languages */
502 
503 
504  /*
505   * See if we have a cupsLanguages attribute...
506   */
507 
508   if ((attr = ppdFindAttr(ppd, "cupsLanguages", NULL)) == NULL || !attr->value)
509     return (NULL);
510 
511  /*
512   * Yes, load the list...
513   */
514 
515   if ((languages = cupsArrayNew((cups_array_func_t)strcmp, NULL)) == NULL)
516     return (NULL);
517 
518   if ((value = strdup(attr->value)) == NULL)
519   {
520     cupsArrayDelete(languages);
521     return (NULL);
522   }
523 
524   for (ptr = value; *ptr;)
525   {
526    /*
527     * Skip leading whitespace...
528     */
529 
530     while (_cups_isspace(*ptr))
531       ptr ++;
532 
533     if (!*ptr)
534       break;
535 
536    /*
537     * Find the end of this language name...
538     */
539 
540     for (start = ptr; *ptr && !_cups_isspace(*ptr); ptr ++);
541 
542     if (*ptr)
543       *ptr++ = '\0';
544 
545     if (!strcmp(start, "en"))
546       continue;
547 
548     cupsArrayAdd(languages, strdup(start));
549   }
550 
551  /*
552   * Free the temporary string and return either an array with one or more
553   * values or a NULL pointer...
554   */
555 
556   free(value);
557 
558   if (cupsArrayCount(languages) == 0)
559   {
560     cupsArrayDelete(languages);
561     return (NULL);
562   }
563   else
564     return (languages);
565 }
566 
567 
568 /*
569  * '_ppdHashName()' - Generate a hash value for a device or profile name.
570  *
571  * This function is primarily used on macOS, but is generally accessible
572  * since cupstestppd needs to check for profile name collisions in PPD files...
573  */
574 
575 unsigned				/* O - Hash value */
_ppdHashName(const char * name)576 _ppdHashName(const char *name)		/* I - Name to hash */
577 {
578   unsigned	mult,			/* Multiplier */
579 		hash = 0;		/* Hash value */
580 
581 
582   for (mult = 1; *name && mult <= 128; mult ++, name ++)
583     hash += (*name & 255) * mult;
584 
585   return (hash);
586 }
587 
588 
589 /*
590  * '_ppdLocalizedAttr()' - Find a localized attribute.
591  */
592 
593 ppd_attr_t *				/* O - Localized attribute or NULL */
_ppdLocalizedAttr(ppd_file_t * ppd,const char * keyword,const char * spec,const char * ll_CC)594 _ppdLocalizedAttr(ppd_file_t *ppd,	/* I - PPD file */
595 		  const char *keyword,	/* I - Main keyword */
596 		  const char *spec,	/* I - Option keyword */
597 		  const char *ll_CC)	/* I - Language + country locale */
598 {
599   char		lkeyword[PPD_MAX_NAME];	/* Localization keyword */
600   ppd_attr_t	*attr;			/* Current attribute */
601 
602 
603   DEBUG_printf(("4_ppdLocalizedAttr(ppd=%p, keyword=\"%s\", spec=\"%s\", "
604                 "ll_CC=\"%s\")", ppd, keyword, spec, ll_CC));
605 
606  /*
607   * Look for Keyword.ll_CC, then Keyword.ll...
608   */
609 
610   snprintf(lkeyword, sizeof(lkeyword), "%s.%s", ll_CC, keyword);
611   if ((attr = ppdFindAttr(ppd, lkeyword, spec)) == NULL)
612   {
613    /*
614     * <rdar://problem/22130168>
615     *
616     * Multiple locales need special handling...  Sigh...
617     */
618 
619     if (!strcmp(ll_CC, "zh_HK"))
620     {
621       snprintf(lkeyword, sizeof(lkeyword), "zh_TW.%s", keyword);
622       attr = ppdFindAttr(ppd, lkeyword, spec);
623     }
624 
625     if (!attr)
626     {
627       snprintf(lkeyword, sizeof(lkeyword), "%2.2s.%s", ll_CC, keyword);
628       attr = ppdFindAttr(ppd, lkeyword, spec);
629     }
630 
631     if (!attr)
632     {
633       if (!strncmp(ll_CC, "ja", 2))
634       {
635        /*
636 	* Due to a bug in the CUPS DDK 1.1.0 ppdmerge program, Japanese
637 	* PPD files were incorrectly assigned "jp" as the locale name
638 	* instead of "ja".  Support both the old (incorrect) and new
639 	* locale names for Japanese...
640 	*/
641 
642 	snprintf(lkeyword, sizeof(lkeyword), "jp.%s", keyword);
643 	attr = ppdFindAttr(ppd, lkeyword, spec);
644       }
645       else if (!strncmp(ll_CC, "nb", 2))
646       {
647        /*
648 	* Norway has two languages, "Bokmal" (the primary one)
649 	* and "Nynorsk" (new Norwegian); this code maps from the (currently)
650 	* recommended "nb" to the previously recommended "no"...
651 	*/
652 
653 	snprintf(lkeyword, sizeof(lkeyword), "no.%s", keyword);
654 	attr = ppdFindAttr(ppd, lkeyword, spec);
655       }
656       else if (!strncmp(ll_CC, "no", 2))
657       {
658        /*
659 	* Norway has two languages, "Bokmal" (the primary one)
660 	* and "Nynorsk" (new Norwegian); we map "no" to "nb" here as
661 	* recommended by the locale folks...
662 	*/
663 
664 	snprintf(lkeyword, sizeof(lkeyword), "nb.%s", keyword);
665 	attr = ppdFindAttr(ppd, lkeyword, spec);
666       }
667     }
668   }
669 
670 #ifdef DEBUG
671   if (attr)
672     DEBUG_printf(("5_ppdLocalizedAttr: *%s %s/%s: \"%s\"\n", attr->name,
673                   attr->spec, attr->text, attr->value ? attr->value : ""));
674   else
675     DEBUG_puts("5_ppdLocalizedAttr: NOT FOUND");
676 #endif /* DEBUG */
677 
678   return (attr);
679 }
680 
681 
682 /*
683  * 'ppd_ll_CC()' - Get the current locale names.
684  */
685 
686 static cups_lang_t *			/* O - Current language */
ppd_ll_CC(char * ll_CC,size_t ll_CC_size)687 ppd_ll_CC(char   *ll_CC,		/* O - Country-specific locale name */
688           size_t ll_CC_size)		/* I - Size of country-specific name */
689 {
690   cups_lang_t	*lang;			/* Current language */
691 
692 
693  /*
694   * Get the current locale...
695   */
696 
697   if ((lang = cupsLangDefault()) == NULL)
698   {
699     strlcpy(ll_CC, "en_US", ll_CC_size);
700     return (NULL);
701   }
702 
703  /*
704   * Copy the locale name...
705   */
706 
707   strlcpy(ll_CC, lang->language, ll_CC_size);
708 
709   if (strlen(ll_CC) == 2)
710   {
711    /*
712     * Map "ll" to primary/origin country locales to have the best
713     * chance of finding a match...
714     */
715 
716     if (!strcmp(ll_CC, "cs"))
717       strlcpy(ll_CC, "cs_CZ", ll_CC_size);
718     else if (!strcmp(ll_CC, "en"))
719       strlcpy(ll_CC, "en_US", ll_CC_size);
720     else if (!strcmp(ll_CC, "ja"))
721       strlcpy(ll_CC, "ja_JP", ll_CC_size);
722     else if (!strcmp(ll_CC, "sv"))
723       strlcpy(ll_CC, "sv_SE", ll_CC_size);
724     else if (!strcmp(ll_CC, "zh"))	/* Simplified Chinese */
725       strlcpy(ll_CC, "zh_CN", ll_CC_size);
726   }
727 
728   DEBUG_printf(("8ppd_ll_CC: lang->language=\"%s\", ll_CC=\"%s\"...",
729                 lang->language, ll_CC));
730   return (lang);
731 }
732