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