1 /*
2  * Destination localization support for CUPS.
3  *
4  * Copyright 2012-2014 by Apple Inc.
5  *
6  * These coded instructions, statements, and computer programs are the
7  * property of Apple Inc. and are protected by Federal copyright
8  * law.  Distribution and use rights are outlined in the file "LICENSE.txt"
9  * which should have been included with this file.  If this file is
10  * missing or damaged, see the license at "http://www.cups.org/".
11  *
12  * This file is subject to the Apple OS-Developed Software exception.
13  */
14 
15 /*
16  * Include necessary headers...
17  */
18 
19 #include "cups-private.h"
20 
21 
22 /*
23  * Local functions...
24  */
25 
26 static void	cups_create_localizations(http_t *http, cups_dinfo_t *dinfo);
27 static int	cups_read_strings(cups_file_t *fp, char *buffer, size_t bufsize,
28 		                  char **id, char **str);
29 static char	*cups_scan_strings(char *buffer);
30 
31 
32 /*
33  * 'cupsLocalizeDestMedia()' - Get the localized string for a destination media
34  *                             size.
35  *
36  * The returned string is stored in the destination information and will become
37  * invalid if the destination information is deleted.
38  *
39  * @since CUPS 2.0/macOS 10.10@
40  */
41 
42 const char *				/* O - Localized string */
cupsLocalizeDestMedia(http_t * http,cups_dest_t * dest,cups_dinfo_t * dinfo,unsigned flags,cups_size_t * size)43 cupsLocalizeDestMedia(
44     http_t       *http,			/* I - Connection to destination */
45     cups_dest_t  *dest,			/* I - Destination */
46     cups_dinfo_t *dinfo,		/* I - Destination information */
47     unsigned     flags,			/* I - Media flags */
48     cups_size_t  *size)			/* I - Media size */
49 {
50   cups_lang_t		*lang;		/* Standard localizations */
51   _cups_message_t	key,		/* Search key */
52 			*match;		/* Matching entry */
53   pwg_media_t		*pwg;		/* PWG media information */
54   cups_array_t		*db;		/* Media database */
55   _cups_media_db_t	*mdb;		/* Media database entry */
56   char			name[1024],	/* Size name */
57 			temp[256];	/* Temporary string */
58   const char		*lsize,		/* Localized media size */
59 			*lsource,	/* Localized media source */
60 			*ltype;		/* Localized media type */
61 
62 
63  /*
64   * Range check input...
65   */
66 
67   if (!http || !dest || !dinfo || !size)
68   {
69     _cupsSetError(IPP_STATUS_ERROR_INTERNAL, strerror(EINVAL), 0);
70     return (NULL);
71   }
72 
73  /*
74   * See if the localization is cached...
75   */
76 
77   if (!dinfo->localizations)
78     cups_create_localizations(http, dinfo);
79 
80   key.id = size->media;
81   if ((match = (_cups_message_t *)cupsArrayFind(dinfo->localizations, &key)) != NULL)
82     return (match->str);
83 
84  /*
85   * If not, get the localized size, source, and type strings...
86   */
87 
88   lang = cupsLangDefault();
89   pwg  = pwgMediaForSize(size->width, size->length);
90 
91   if (pwg->ppd)
92     lsize = _cupsLangString(lang, pwg->ppd);
93   else
94     lsize = NULL;
95 
96   if (!lsize)
97   {
98     if ((size->width % 635) == 0 && (size->length % 635) == 0)
99     {
100      /*
101       * Use inches since the size is a multiple of 1/4 inch.
102       */
103 
104       snprintf(temp, sizeof(temp), _cupsLangString(lang, _("%g x %g")), size->width / 2540.0, size->length / 2540.0);
105     }
106     else
107     {
108      /*
109       * Use millimeters since the size is not a multiple of 1/4 inch.
110       */
111 
112       snprintf(temp, sizeof(temp), _cupsLangString(lang, _("%d x %d mm")), (size->width + 50) / 100, (size->length + 50) / 100);
113     }
114 
115     lsize = temp;
116   }
117 
118   if (flags & CUPS_MEDIA_FLAGS_READY)
119     db = dinfo->ready_db;
120   else
121     db = dinfo->media_db;
122 
123   DEBUG_printf(("1cupsLocalizeDestMedia: size->media=\"%s\"", size->media));
124 
125   for (mdb = (_cups_media_db_t *)cupsArrayFirst(db); mdb; mdb = (_cups_media_db_t *)cupsArrayNext(db))
126   {
127     if (mdb->key && !strcmp(mdb->key, size->media))
128       break;
129     else if (mdb->size_name && !strcmp(mdb->size_name, size->media))
130       break;
131   }
132 
133   if (!mdb)
134   {
135     for (mdb = (_cups_media_db_t *)cupsArrayFirst(db); mdb; mdb = (_cups_media_db_t *)cupsArrayNext(db))
136     {
137       if (mdb->width == size->width && mdb->length == size->length && mdb->bottom == size->bottom && mdb->left == size->left && mdb->right == size->right && mdb->top == size->top)
138 	break;
139     }
140   }
141 
142   if (mdb)
143   {
144     DEBUG_printf(("1cupsLocalizeDestMedia: MATCH mdb%p [key=\"%s\" size_name=\"%s\" source=\"%s\" type=\"%s\" width=%d length=%d B%d L%d R%d T%d]", (void *)mdb, mdb->key, mdb->size_name, mdb->source, mdb->type, mdb->width, mdb->length, mdb->bottom, mdb->left, mdb->right, mdb->top));
145 
146     lsource = cupsLocalizeDestValue(http, dest, dinfo, "media-source", mdb->source);
147     ltype   = cupsLocalizeDestValue(http, dest, dinfo, "media-type", mdb->type);
148   }
149   else
150   {
151     lsource = NULL;
152     ltype   = NULL;
153   }
154 
155   if (!lsource && !ltype)
156   {
157     if (size->bottom || size->left || size->right || size->top)
158       snprintf(name, sizeof(name), _cupsLangString(lang, _("%s (Borderless)")), lsize);
159     else
160       strlcpy(name, lsize, sizeof(name));
161   }
162   else if (!lsource)
163   {
164     if (size->bottom || size->left || size->right || size->top)
165       snprintf(name, sizeof(name), _cupsLangString(lang, _("%s (Borderless, %s)")), lsize, ltype);
166     else
167       snprintf(name, sizeof(name), _cupsLangString(lang, _("%s (%s)")), lsize, ltype);
168   }
169   else if (!ltype)
170   {
171     if (size->bottom || size->left || size->right || size->top)
172       snprintf(name, sizeof(name), _cupsLangString(lang, _("%s (Borderless, %s)")), lsize, lsource);
173     else
174       snprintf(name, sizeof(name), _cupsLangString(lang, _("%s (%s)")), lsize, lsource);
175   }
176   else
177   {
178     if (size->bottom || size->left || size->right || size->top)
179       snprintf(name, sizeof(name), _cupsLangString(lang, _("%s (Borderless, %s, %s)")), lsize, ltype, lsource);
180     else
181       snprintf(name, sizeof(name), _cupsLangString(lang, _("%s (%s, %s)")), lsize, ltype, lsource);
182   }
183 
184   if ((match = (_cups_message_t *)calloc(1, sizeof(_cups_message_t))) == NULL)
185     return (NULL);
186 
187   match->id  = strdup(size->media);
188   match->str = strdup(name);
189 
190   cupsArrayAdd(dinfo->localizations, match);
191 
192   return (match->str);
193 }
194 
195 
196 /*
197  * 'cupsLocalizeDestOption()' - Get the localized string for a destination
198  *                              option.
199  *
200  * The returned string is stored in the destination information and will become
201  * invalid if the destination information is deleted.
202  *
203  * @since CUPS 1.6/macOS 10.8@
204  */
205 
206 const char *				/* O - Localized string */
cupsLocalizeDestOption(http_t * http,cups_dest_t * dest,cups_dinfo_t * dinfo,const char * option)207 cupsLocalizeDestOption(
208     http_t       *http,			/* I - Connection to destination */
209     cups_dest_t  *dest,			/* I - Destination */
210     cups_dinfo_t *dinfo,		/* I - Destination information */
211     const char   *option)		/* I - Option to localize */
212 {
213   _cups_message_t	key,		/* Search key */
214 			*match;		/* Matching entry */
215 
216 
217   if (!http || !dest || !dinfo)
218     return (option);
219 
220   if (!dinfo->localizations)
221     cups_create_localizations(http, dinfo);
222 
223   if (cupsArrayCount(dinfo->localizations) == 0)
224     return (option);
225 
226   key.id = (char *)option;
227   if ((match = (_cups_message_t *)cupsArrayFind(dinfo->localizations,
228                                                 &key)) != NULL)
229     return (match->str);
230   else
231     return (option);
232 }
233 
234 
235 /*
236  * 'cupsLocalizeDestValue()' - Get the localized string for a destination
237  *                             option+value pair.
238  *
239  * The returned string is stored in the destination information and will become
240  * invalid if the destination information is deleted.
241  *
242  * @since CUPS 1.6/macOS 10.8@
243  */
244 
245 const char *				/* O - Localized string */
cupsLocalizeDestValue(http_t * http,cups_dest_t * dest,cups_dinfo_t * dinfo,const char * option,const char * value)246 cupsLocalizeDestValue(
247     http_t       *http,			/* I - Connection to destination */
248     cups_dest_t  *dest,			/* I - Destination */
249     cups_dinfo_t *dinfo,		/* I - Destination information */
250     const char   *option,		/* I - Option to localize */
251     const char   *value)		/* I - Value to localize */
252 {
253   _cups_message_t	key,		/* Search key */
254 			*match;		/* Matching entry */
255   char			pair[256];	/* option.value pair */
256 
257 
258   if (!http || !dest || !dinfo)
259     return (value);
260 
261   if (!dinfo->localizations)
262     cups_create_localizations(http, dinfo);
263 
264   if (cupsArrayCount(dinfo->localizations) == 0)
265     return (value);
266 
267   snprintf(pair, sizeof(pair), "%s.%s", option, value);
268   key.id = pair;
269   if ((match = (_cups_message_t *)cupsArrayFind(dinfo->localizations,
270                                                 &key)) != NULL)
271     return (match->str);
272   else
273     return (value);
274 }
275 
276 
277 /*
278  * 'cups_create_localizations()' - Create the localizations array for a
279  *                                 destination.
280  */
281 
282 static void
cups_create_localizations(http_t * http,cups_dinfo_t * dinfo)283 cups_create_localizations(
284     http_t       *http,			/* I - Connection to destination */
285     cups_dinfo_t *dinfo)		/* I - Destination informations */
286 {
287   http_t		*http2;		/* Connection for strings file */
288   http_status_t		status;		/* Request status */
289   ipp_attribute_t	*attr;		/* "printer-strings-uri" attribute */
290   char			scheme[32],	/* URI scheme */
291   			userpass[256],	/* Username/password info */
292   			hostname[256],	/* Hostname */
293   			resource[1024],	/* Resource */
294   			http_hostname[256],
295   					/* Hostname of connection */
296 			tempfile[1024];	/* Temporary filename */
297   int			port;		/* Port number */
298   http_encryption_t	encryption;	/* Encryption to use */
299   cups_file_t		*temp;		/* Temporary file */
300 
301 
302  /*
303   * Create an empty message catalog...
304   */
305 
306   dinfo->localizations = _cupsMessageNew(NULL);
307 
308  /*
309   * See if there are any localizations...
310   */
311 
312   if ((attr = ippFindAttribute(dinfo->attrs, "printer-strings-uri",
313                                IPP_TAG_URI)) == NULL)
314   {
315    /*
316     * Nope...
317     */
318 
319     DEBUG_puts("4cups_create_localizations: No printer-strings-uri (uri) "
320                "value.");
321     return;				/* Nope */
322   }
323 
324  /*
325   * Pull apart the URI and determine whether we need to try a different
326   * server...
327   */
328 
329   if (httpSeparateURI(HTTP_URI_CODING_ALL, attr->values[0].string.text,
330                       scheme, sizeof(scheme), userpass, sizeof(userpass),
331                       hostname, sizeof(hostname), &port, resource,
332                       sizeof(resource)) < HTTP_URI_STATUS_OK)
333   {
334     DEBUG_printf(("4cups_create_localizations: Bad printer-strings-uri value "
335                   "\"%s\".", attr->values[0].string.text));
336     return;
337   }
338 
339   httpGetHostname(http, http_hostname, sizeof(http_hostname));
340 
341   if (!_cups_strcasecmp(http_hostname, hostname) &&
342       port == httpAddrPort(http->hostaddr))
343   {
344    /*
345     * Use the same connection...
346     */
347 
348     http2 = http;
349   }
350   else
351   {
352    /*
353     * Connect to the alternate host...
354     */
355 
356     if (!strcmp(scheme, "https"))
357       encryption = HTTP_ENCRYPTION_ALWAYS;
358     else
359       encryption = HTTP_ENCRYPTION_IF_REQUESTED;
360 
361     if ((http2 = httpConnect2(hostname, port, NULL, AF_UNSPEC, encryption, 1,
362                               30000, NULL)) == NULL)
363     {
364       DEBUG_printf(("4cups_create_localizations: Unable to connect to "
365                     "%s:%d: %s", hostname, port, cupsLastErrorString()));
366       return;
367     }
368   }
369 
370  /*
371   * Get a temporary file...
372   */
373 
374   if ((temp = cupsTempFile2(tempfile, sizeof(tempfile))) == NULL)
375   {
376     DEBUG_printf(("4cups_create_localizations: Unable to create temporary "
377                   "file: %s", cupsLastErrorString()));
378     if (http2 != http)
379       httpClose(http2);
380     return;
381   }
382 
383   status = cupsGetFd(http2, resource, cupsFileNumber(temp));
384 
385   DEBUG_printf(("4cups_create_localizations: GET %s = %s", resource,
386                 httpStatus(status)));
387 
388   if (status == HTTP_STATUS_OK)
389   {
390    /*
391     * Got the file, read it...
392     */
393 
394     char		buffer[8192],	/* Message buffer */
395     			*id,		/* ID string */
396     			*str;		/* Translated message */
397     _cups_message_t	*m;		/* Current message */
398 
399     lseek(cupsFileNumber(temp), 0, SEEK_SET);
400 
401     while (cups_read_strings(temp, buffer, sizeof(buffer), &id, &str))
402     {
403       if ((m = malloc(sizeof(_cups_message_t))) == NULL)
404         break;
405 
406       m->id  = strdup(id);
407       m->str = strdup(str);
408 
409       if (m->id && m->str)
410         cupsArrayAdd(dinfo->localizations, m);
411       else
412       {
413         if (m->id)
414           free(m->id);
415 
416         if (m->str)
417           free(m->str);
418 
419         free(m);
420         break;
421       }
422     }
423   }
424 
425   DEBUG_printf(("4cups_create_localizations: %d messages loaded.",
426                 cupsArrayCount(dinfo->localizations)));
427 
428  /*
429   * Cleanup...
430   */
431 
432   unlink(tempfile);
433   cupsFileClose(temp);
434 
435   if (http2 != http)
436     httpClose(http2);
437 }
438 
439 
440 /*
441  * 'cups_read_strings()' - Read a pair of strings from a .strings file.
442  */
443 
444 static int				/* O - 1 on success, 0 on failure */
cups_read_strings(cups_file_t * strings,char * buffer,size_t bufsize,char ** id,char ** str)445 cups_read_strings(cups_file_t *strings,	/* I - .strings file */
446                   char        *buffer,	/* I - Line buffer */
447                   size_t      bufsize,	/* I - Size of line buffer */
448 		  char        **id,	/* O - Pointer to ID string */
449 		  char        **str)	/* O - Pointer to translation string */
450 {
451   char	*bufptr;			/* Pointer into buffer */
452 
453 
454   while (cupsFileGets(strings, buffer, bufsize))
455   {
456     if (buffer[0] != '\"')
457       continue;
458 
459     *id    = buffer + 1;
460     bufptr = cups_scan_strings(buffer);
461 
462     if (*bufptr != '\"')
463       continue;
464 
465     *bufptr++ = '\0';
466 
467     while (*bufptr && *bufptr != '\"')
468       bufptr ++;
469 
470     if (!*bufptr)
471       continue;
472 
473     *str   = bufptr + 1;
474     bufptr = cups_scan_strings(bufptr);
475 
476     if (*bufptr != '\"')
477       continue;
478 
479     *bufptr = '\0';
480 
481     return (1);
482   }
483 
484   return (0);
485 }
486 
487 
488 /*
489  * 'cups_scan_strings()' - Scan a quoted string.
490  */
491 
492 static char *				/* O - End of string */
cups_scan_strings(char * buffer)493 cups_scan_strings(char *buffer)		/* I - Start of string */
494 {
495   char	*bufptr;			/* Pointer into string */
496 
497 
498   for (bufptr = buffer + 1; *bufptr && *bufptr != '\"'; bufptr ++)
499   {
500     if (*bufptr == '\\')
501     {
502       if (bufptr[1] >= '0' && bufptr[1] <= '3' &&
503 	  bufptr[2] >= '0' && bufptr[2] <= '7' &&
504 	  bufptr[3] >= '0' && bufptr[3] <= '7')
505       {
506        /*
507 	* Decode \nnn octal escape...
508 	*/
509 
510 	*bufptr = (char)(((((bufptr[1] - '0') << 3) | (bufptr[2] - '0')) << 3) | (bufptr[3] - '0'));
511 	_cups_strcpy(bufptr + 1, bufptr + 4);
512       }
513       else
514       {
515        /*
516 	* Decode \C escape...
517 	*/
518 
519 	_cups_strcpy(bufptr, bufptr + 1);
520 	if (*bufptr == 'n')
521 	  *bufptr = '\n';
522 	else if (*bufptr == 'r')
523 	  *bufptr = '\r';
524 	else if (*bufptr == 't')
525 	  *bufptr = '\t';
526       }
527     }
528   }
529 
530   return (bufptr);
531 }
532