1 /*
2 * Destination localization support for CUPS.
3 *
4 * Copyright © 2020-2024 by OpenPrinting.
5 * Copyright © 2012-2017 by Apple Inc.
6 *
7 * Licensed under Apache License v2.0. See the file "LICENSE" for more
8 * information.
9 */
10
11 /*
12 * Include necessary headers...
13 */
14
15 #include "cups-private.h"
16 #include "debug-internal.h"
17
18
19 /*
20 * Local functions...
21 */
22
23 static void cups_create_localizations(http_t *http, cups_dinfo_t *dinfo);
24
25
26 /*
27 * 'cupsLocalizeDestMedia()' - Get the localized string for a destination media
28 * size.
29 *
30 * The returned string is stored in the destination information and will become
31 * invalid if the destination information is deleted.
32 *
33 * @since CUPS 2.0/macOS 10.10@
34 */
35
36 const char * /* O - Localized string */
cupsLocalizeDestMedia(http_t * http,cups_dest_t * dest,cups_dinfo_t * dinfo,unsigned flags,cups_size_t * size)37 cupsLocalizeDestMedia(
38 http_t *http, /* I - Connection to destination */
39 cups_dest_t *dest, /* I - Destination */
40 cups_dinfo_t *dinfo, /* I - Destination information */
41 unsigned flags, /* I - Media flags */
42 cups_size_t *size) /* I - Media size */
43 {
44 cups_lang_t *lang; /* Standard localizations */
45 _cups_message_t key, /* Search key */
46 *match; /* Matching entry */
47 pwg_media_t *pwg; /* PWG media information */
48 cups_array_t *db; /* Media database */
49 _cups_media_db_t *mdb; /* Media database entry */
50 char lstr[1024], /* Localized size name */
51 temp[256]; /* Temporary string */
52 const char *lsize, /* Localized media size */
53 *lsource, /* Localized media source */
54 *ltype; /* Localized media type */
55
56
57 DEBUG_printf(("cupsLocalizeDestMedia(http=%p, dest=%p, dinfo=%p, flags=%x, size=%p(\"%s\"))", (void *)http, (void *)dest, (void *)dinfo, flags, (void *)size, size ? size->media : "(null)"));
58
59 /*
60 * Range check input...
61 */
62
63 if (!http || !dest || !dinfo || !size)
64 {
65 DEBUG_puts("1cupsLocalizeDestMedia: Returning NULL.");
66 _cupsSetError(IPP_STATUS_ERROR_INTERNAL, strerror(EINVAL), 0);
67 return (NULL);
68 }
69
70 /*
71 * Find the matching media database entry...
72 */
73
74 if (flags & CUPS_MEDIA_FLAGS_READY)
75 db = dinfo->ready_db;
76 else
77 db = dinfo->media_db;
78
79 DEBUG_printf(("1cupsLocalizeDestMedia: size->media=\"%s\"", size->media));
80
81 for (mdb = (_cups_media_db_t *)cupsArrayFirst(db); mdb; mdb = (_cups_media_db_t *)cupsArrayNext(db))
82 {
83 if (mdb->key && !strcmp(mdb->key, size->media))
84 break;
85 else if (mdb->size_name && !strcmp(mdb->size_name, size->media))
86 break;
87 }
88
89 if (!mdb)
90 {
91 for (mdb = (_cups_media_db_t *)cupsArrayFirst(db); mdb; mdb = (_cups_media_db_t *)cupsArrayNext(db))
92 {
93 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)
94 break;
95 }
96 }
97
98 /*
99 * See if the localization is cached...
100 */
101
102 lang = cupsLangDefault();
103
104 if (!dinfo->localizations)
105 cups_create_localizations(http, dinfo);
106
107 snprintf(temp, sizeof(temp), "media.%s", size->media);
108 key.msg = temp;
109
110 if ((match = (_cups_message_t *)cupsArrayFind(dinfo->localizations, &key)) != NULL)
111 {
112 lsize = match->str;
113 }
114 else
115 {
116 /*
117 * Not a media name, try a media-key name...
118 */
119
120 snprintf(temp, sizeof(temp), "media-key.%s", size->media);
121 if ((match = (_cups_message_t *)cupsArrayFind(dinfo->localizations, &key)) != NULL)
122 lsize = match->str;
123 else
124 lsize = NULL;
125 }
126
127 if (!lsize && (pwg = pwgMediaForSize(size->width, size->length)) != NULL && pwg->ppd)
128 {
129 /*
130 * Get a standard localization...
131 */
132
133 snprintf(temp, sizeof(temp), "media.%s", pwg->pwg);
134 if ((lsize = _cupsLangString(lang, temp)) == temp)
135 lsize = NULL;
136 }
137
138 if (!lsize)
139 {
140 /*
141 * Make a dimensional localization...
142 */
143
144 if ((size->width % 635) == 0 && (size->length % 635) == 0)
145 {
146 /*
147 * Use inches since the size is a multiple of 1/4 inch.
148 */
149
150 snprintf(temp, sizeof(temp), _cupsLangString(lang, _("%g x %g \"")), size->width / 2540.0, size->length / 2540.0);
151 }
152 else
153 {
154 /*
155 * Use millimeters since the size is not a multiple of 1/4 inch.
156 */
157
158 snprintf(temp, sizeof(temp), _cupsLangString(lang, _("%d x %d mm")), (size->width + 50) / 100, (size->length + 50) / 100);
159 }
160
161 lsize = temp;
162 }
163
164 if (mdb)
165 {
166 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));
167
168 if ((lsource = cupsLocalizeDestValue(http, dest, dinfo, "media-source", mdb->source)) == mdb->source && mdb->source)
169 lsource = _cupsLangString(lang, _("Other Tray"));
170 if ((ltype = cupsLocalizeDestValue(http, dest, dinfo, "media-type", mdb->type)) == mdb->type && mdb->type)
171 ltype = _cupsLangString(lang, _("Other Media"));
172 }
173 else
174 {
175 lsource = NULL;
176 ltype = NULL;
177 }
178
179 if (!lsource && !ltype)
180 {
181 if (!size->bottom && !size->left && !size->right && !size->top)
182 snprintf(lstr, sizeof(lstr), _cupsLangString(lang, _("%s (Borderless)")), lsize);
183 else
184 strlcpy(lstr, lsize, sizeof(lstr));
185 }
186 else if (!lsource)
187 {
188 if (!size->bottom && !size->left && !size->right && !size->top)
189 snprintf(lstr, sizeof(lstr), _cupsLangString(lang, _("%s (Borderless, %s)")), lsize, ltype);
190 else
191 snprintf(lstr, sizeof(lstr), _cupsLangString(lang, _("%s (%s)")), lsize, ltype);
192 }
193 else if (!ltype)
194 {
195 if (!size->bottom && !size->left && !size->right && !size->top)
196 snprintf(lstr, sizeof(lstr), _cupsLangString(lang, _("%s (Borderless, %s)")), lsize, lsource);
197 else
198 snprintf(lstr, sizeof(lstr), _cupsLangString(lang, _("%s (%s)")), lsize, lsource);
199 }
200 else
201 {
202 if (!size->bottom && !size->left && !size->right && !size->top)
203 snprintf(lstr, sizeof(lstr), _cupsLangString(lang, _("%s (Borderless, %s, %s)")), lsize, ltype, lsource);
204 else
205 snprintf(lstr, sizeof(lstr), _cupsLangString(lang, _("%s (%s, %s)")), lsize, ltype, lsource);
206 }
207
208 if ((match = (_cups_message_t *)calloc(1, sizeof(_cups_message_t))) == NULL)
209 return (NULL);
210
211 match->msg = strdup(size->media);
212 match->str = strdup(lstr);
213
214 cupsArrayAdd(dinfo->localizations, match);
215
216 DEBUG_printf(("1cupsLocalizeDestMedia: Returning \"%s\".", match->str));
217
218 return (match->str);
219 }
220
221
222 /*
223 * 'cupsLocalizeDestOption()' - Get the localized string for a destination
224 * option.
225 *
226 * The returned string is stored in the destination information and will become
227 * invalid if the destination information is deleted.
228 *
229 * @since CUPS 1.6/macOS 10.8@
230 */
231
232 const char * /* O - Localized string */
cupsLocalizeDestOption(http_t * http,cups_dest_t * dest,cups_dinfo_t * dinfo,const char * option)233 cupsLocalizeDestOption(
234 http_t *http, /* I - Connection to destination */
235 cups_dest_t *dest, /* I - Destination */
236 cups_dinfo_t *dinfo, /* I - Destination information */
237 const char *option) /* I - Option to localize */
238 {
239 _cups_message_t key, /* Search key */
240 *match; /* Matching entry */
241 const char *localized; /* Localized string */
242
243
244 DEBUG_printf(("cupsLocalizeDestOption(http=%p, dest=%p, dinfo=%p, option=\"%s\")", (void *)http, (void *)dest, (void *)dinfo, option));
245
246 if (!http || !dest || !dinfo)
247 return (option);
248
249 if (!dinfo->localizations)
250 cups_create_localizations(http, dinfo);
251
252 key.msg = (char *)option;
253 if ((match = (_cups_message_t *)cupsArrayFind(dinfo->localizations, &key)) != NULL)
254 return (match->str);
255 else if ((localized = _cupsLangString(cupsLangDefault(), option)) != NULL)
256 return (localized);
257 else
258 return (option);
259 }
260
261
262 /*
263 * 'cupsLocalizeDestValue()' - Get the localized string for a destination
264 * option+value pair.
265 *
266 * The returned string is stored in the destination information and will become
267 * invalid if the destination information is deleted.
268 *
269 * @since CUPS 1.6/macOS 10.8@
270 */
271
272 const char * /* O - Localized string */
cupsLocalizeDestValue(http_t * http,cups_dest_t * dest,cups_dinfo_t * dinfo,const char * option,const char * value)273 cupsLocalizeDestValue(
274 http_t *http, /* I - Connection to destination */
275 cups_dest_t *dest, /* I - Destination */
276 cups_dinfo_t *dinfo, /* I - Destination information */
277 const char *option, /* I - Option to localize */
278 const char *value) /* I - Value to localize */
279 {
280 _cups_message_t key, /* Search key */
281 *match; /* Matching entry */
282 char pair[256]; /* option.value pair */
283 const char *localized; /* Localized string */
284
285
286 DEBUG_printf(("cupsLocalizeDestValue(http=%p, dest=%p, dinfo=%p, option=\"%s\", value=\"%s\")", (void *)http, (void *)dest, (void *)dinfo, option, value));
287
288 if (!http || !dest || !dinfo)
289 return (value);
290
291 if (!strcmp(option, "media"))
292 {
293 pwg_media_t *media = pwgMediaForPWG(value);
294 cups_size_t size;
295
296 strlcpy(size.media, value, sizeof(size.media));
297 size.width = media ? media->width : 0;
298 size.length = media ? media->length : 0;
299 size.left = 0;
300 size.right = 0;
301 size.bottom = 0;
302 size.top = 0;
303
304 return (cupsLocalizeDestMedia(http, dest, dinfo, CUPS_MEDIA_FLAGS_DEFAULT, &size));
305 }
306
307 if (!dinfo->localizations)
308 cups_create_localizations(http, dinfo);
309
310 snprintf(pair, sizeof(pair), "%s.%s", option, value);
311 key.msg = pair;
312 if ((match = (_cups_message_t *)cupsArrayFind(dinfo->localizations, &key)) != NULL)
313 return (match->str);
314 else if ((localized = _cupsLangString(cupsLangDefault(), pair)) != NULL && strcmp(localized, pair))
315 return (localized);
316 else
317 return (value);
318 }
319
320
321 /*
322 * 'cups_create_localizations()' - Create the localizations array for a
323 * destination.
324 */
325
326 static void
cups_create_localizations(http_t * http,cups_dinfo_t * dinfo)327 cups_create_localizations(
328 http_t *http, /* I - Connection to destination */
329 cups_dinfo_t *dinfo) /* I - Destination information */
330 {
331 http_t *http2; /* Connection for strings file */
332 http_status_t status; /* Request status */
333 ipp_attribute_t *attr; /* "printer-strings-uri" attribute */
334 char scheme[32], /* URI scheme */
335 userpass[256], /* Username/password info */
336 hostname[256], /* Hostname */
337 resource[1024], /* Resource */
338 http_hostname[256],
339 /* Hostname of connection */
340 tempfile[1024]; /* Temporary filename */
341 int port; /* Port number */
342 http_encryption_t encryption; /* Encryption to use */
343 cups_file_t *temp; /* Temporary file */
344
345
346 /*
347 * See if there are any localizations...
348 */
349
350 if ((attr = ippFindAttribute(dinfo->attrs, "printer-strings-uri",
351 IPP_TAG_URI)) == NULL)
352 {
353 /*
354 * Nope, create an empty message catalog...
355 */
356
357 dinfo->localizations = _cupsMessageNew(NULL);
358 DEBUG_puts("4cups_create_localizations: No printer-strings-uri (uri) value.");
359 return;
360 }
361
362 /*
363 * Pull apart the URI and determine whether we need to try a different
364 * server...
365 */
366
367 if (httpSeparateURI(HTTP_URI_CODING_ALL, attr->values[0].string.text,
368 scheme, sizeof(scheme), userpass, sizeof(userpass),
369 hostname, sizeof(hostname), &port, resource,
370 sizeof(resource)) < HTTP_URI_STATUS_OK)
371 {
372 dinfo->localizations = _cupsMessageNew(NULL);
373 DEBUG_printf(("4cups_create_localizations: Bad printer-strings-uri value \"%s\".", attr->values[0].string.text));
374 return;
375 }
376
377 httpGetHostname(http, http_hostname, sizeof(http_hostname));
378
379 if (!_cups_strcasecmp(http_hostname, hostname) &&
380 port == httpAddrPort(http->hostaddr))
381 {
382 /*
383 * Use the same connection...
384 */
385
386 http2 = http;
387 }
388 else
389 {
390 /*
391 * Connect to the alternate host...
392 */
393
394 if (!strcmp(scheme, "https"))
395 encryption = HTTP_ENCRYPTION_ALWAYS;
396 else
397 encryption = HTTP_ENCRYPTION_IF_REQUESTED;
398
399 if ((http2 = httpConnect2(hostname, port, NULL, AF_UNSPEC, encryption, 1,
400 30000, NULL)) == NULL)
401 {
402 DEBUG_printf(("4cups_create_localizations: Unable to connect to "
403 "%s:%d: %s", hostname, port, cupsLastErrorString()));
404 return;
405 }
406 }
407
408 /*
409 * Get a temporary file...
410 */
411
412 if ((temp = cupsTempFile2(tempfile, sizeof(tempfile))) == NULL)
413 {
414 DEBUG_printf(("4cups_create_localizations: Unable to create temporary "
415 "file: %s", cupsLastErrorString()));
416 if (http2 != http)
417 httpClose(http2);
418 return;
419 }
420
421 status = cupsGetFd(http2, resource, cupsFileNumber(temp));
422 cupsFileClose(temp);
423
424 DEBUG_printf(("4cups_create_localizations: GET %s = %s", resource, httpStatus(status)));
425
426 if (status == HTTP_STATUS_OK)
427 {
428 /*
429 * Got the file, read it...
430 */
431
432 dinfo->localizations = _cupsMessageLoad(tempfile, _CUPS_MESSAGE_STRINGS);
433 }
434
435 DEBUG_printf(("4cups_create_localizations: %d messages loaded.",
436 cupsArrayCount(dinfo->localizations)));
437
438 /*
439 * Cleanup...
440 */
441
442 unlink(tempfile);
443
444 if (http2 != http)
445 httpClose(http2);
446 }
447
448