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