// // Shared message catalog class for the CUPS PPD Compiler. // // Copyright 2007-2017 by Apple Inc. // Copyright 2002-2006 by Easy Software Products. // // Licensed under Apache License v2.0. See the file "LICENSE" for more information. // // // Include necessary headers... // #include "ppdc-private.h" // // Character encodings... // typedef enum { PPDC_CS_AUTO, PPDC_CS_UTF8, PPDC_CS_UTF16BE, PPDC_CS_UTF16LE } ppdc_cs_t; // // Local functions... // #if defined(__APPLE__) && defined(CUPS_BUNDLEDIR) static void apple_add_message(CFStringRef key, CFStringRef val, ppdcCatalog *c); #endif /* __APPLE__ && CUPS_BUNDLEDIR */ static int get_utf8(char *&ptr); static int get_utf16(cups_file_t *fp, ppdc_cs_t &cs); static int put_utf8(int ch, char *&ptr, char *end); static int put_utf16(cups_file_t *fp, int ch); // // 'ppdcCatalog::ppdcCatalog()' - Create a shared message catalog. // ppdcCatalog::ppdcCatalog(const char *l, // I - Locale const char *f) // I - Message catalog file : ppdcShared() { PPDC_NEW; locale = new ppdcString(l); filename = new ppdcString(f); messages = new ppdcArray(); if (l && strcmp(l, "en")) { // Try loading the base messages for this locale... char pofile[1024]; // Message catalog file #if defined(__APPLE__) && defined(CUPS_BUNDLEDIR) char applelang[256]; // Apple language ID CFURLRef url; // URL to cups.strings file CFReadStreamRef stream = NULL; // File stream CFPropertyListRef plist = NULL; // Localization file snprintf(pofile, sizeof(pofile), CUPS_BUNDLEDIR "/Resources/%s.lproj/cups.strings", _cupsAppleLanguage(l, applelang, sizeof(applelang))); if (access(pofile, 0)) { // Try alternate lproj directory names... const char *tl = l; // Temporary locale string if (!strncmp(l, "en", 2)) tl = "English"; else if (!strncmp(l, "nb", 2)) tl = "no"; else if (!strncmp(l, "nl", 2)) tl = "Dutch"; else if (!strncmp(l, "fr", 2)) tl = "French"; else if (!strncmp(l, "de", 2)) tl = "German"; else if (!strncmp(l, "it", 2)) tl = "Italian"; else if (!strncmp(l, "ja", 2)) tl = "Japanese"; else if (!strncmp(l, "es", 2)) tl = "Spanish"; snprintf(pofile, sizeof(pofile), CUPS_BUNDLEDIR "/Resources/%s.lproj/cups.strings", tl); } url = CFURLCreateFromFileSystemRepresentation(kCFAllocatorDefault, (UInt8 *)pofile, (CFIndex)strlen(pofile), false); if (url) { stream = CFReadStreamCreateWithFile(kCFAllocatorDefault, url); if (stream) { /* * Read the property list containing the localization data. */ CFReadStreamOpen(stream); plist = CFPropertyListCreateWithStream(kCFAllocatorDefault, stream, 0, kCFPropertyListImmutable, NULL, NULL); if (plist && CFGetTypeID(plist) == CFDictionaryGetTypeID()) CFDictionaryApplyFunction((CFDictionaryRef)plist, (CFDictionaryApplierFunction)apple_add_message, this); if (plist) CFRelease(plist); CFRelease(stream); } CFRelease(url); } #else _cups_globals_t *cg = _cupsGlobals(); // Global information snprintf(pofile, sizeof(pofile), "%s/%s/cups_%s.po", cg->localedir, l, l); if (load_messages(pofile) && strchr(l, '_')) { // Try the base locale... char baseloc[3]; // Base locale... strlcpy(baseloc, l, sizeof(baseloc)); snprintf(pofile, sizeof(pofile), "%s/%s/cups_%s.po", cg->localedir, baseloc, baseloc); load_messages(pofile); } #endif /* __APPLE__ && CUPS_BUNDLEDIR */ } if (f && *f) load_messages(f); } // // 'ppdcCatalog::~ppdcCatalog()' - Destroy a shared message catalog. // ppdcCatalog::~ppdcCatalog() { PPDC_DELETE; locale->release(); filename->release(); messages->release(); } // // 'ppdcCatalog::add_message()' - Add a new message. // void ppdcCatalog::add_message( const char *id, // I - Message ID to add const char *string) // I - Translation string { ppdcMessage *m; // Current message char text[1024]; // Text to translate // Range check input... if (!id) return; // Verify that we don't already have the message ID... for (m = (ppdcMessage *)messages->first(); m; m = (ppdcMessage *)messages->next()) if (!strcmp(m->id->value, id)) { if (string) { m->string->release(); m->string = new ppdcString(string); } return; } // Add the message... if (!string) { snprintf(text, sizeof(text), "TRANSLATE %s", id); string = text; } messages->add(new ppdcMessage(id, string)); } // // 'ppdcCatalog::find_message()' - Find a message in a catalog... // const char * // O - Message text ppdcCatalog::find_message( const char *id) // I - Message ID { ppdcMessage *m; // Current message if (!*id) return (id); for (m = (ppdcMessage *)messages->first(); m; m = (ppdcMessage *)messages->next()) if (!strcmp(m->id->value, id)) return (m->string->value); return (id); } // // 'ppdcCatalog::load_messages()' - Load messages from a .po file. // int // O - 0 on success, -1 on failure ppdcCatalog::load_messages( const char *f) // I - Message catalog file { cups_file_t *fp; // Message file char line[4096], // Line buffer *ptr, // Pointer into buffer id[4096], // Translation ID str[4096]; // Translation string int linenum; // Line number // Open the message catalog file... if ((fp = cupsFileOpen(f, "r")) == NULL) return (-1); if ((ptr = (char *)strrchr(f, '.')) == NULL) goto unknown_load_format; else if (!strcmp(ptr, ".strings")) { /* * Read messages in macOS ".strings" format, which are either UTF-8/UTF-16 * text files of the format: * * "id" = "str"; * * Strings files can also contain C-style comments. */ ppdc_cs_t cs = PPDC_CS_AUTO; // Character set for file int ch; // Current character from file char *end; // End of buffer id[0] = '\0'; str[0] = '\0'; ptr = NULL; end = NULL; while ((ch = get_utf16(fp, cs)) != 0) { if (ptr) { if (ch == '\\') { if ((ch = get_utf16(fp, cs)) == 0) break; if (ch == 'n') ch = '\n'; else if (ch == 't') ch = '\t'; } else if (ch == '\"') { *ptr = '\0'; ptr = NULL; } if (ptr) put_utf8(ch, ptr, end); } else if (ch == '/') { // Start of a comment? if ((ch = get_utf16(fp, cs)) == 0) break; if (ch == '*') { // Skip C comment... int lastch = 0; while ((ch = get_utf16(fp, cs)) != 0) { if (ch == '/' && lastch == '*') break; lastch = ch; } } else if (ch == '/') { // Skip C++ comment... while ((ch = get_utf16(fp, cs)) != 0) if (ch == '\n') break; } } else if (ch == '\"') { // Start quoted string... if (id[0]) { ptr = str; end = str + sizeof(str) - 1; } else { ptr = id; end = id + sizeof(id) - 1; } } else if (ch == ';') { // Add string... add_message(id, str); id[0] = '\0'; } } } else if (!strcmp(ptr, ".po") || !strcmp(ptr, ".gz")) { /* * Read messages from the catalog file until EOF... * * The format is the GNU gettext .po format, which is fairly simple: * * msgid "some text" * msgstr "localized text" * * The ID and localized text can span multiple lines using the form: * * msgid "" * "some long text" * msgstr "" * "localized text spanning " * "multiple lines" */ int which, // In msgid? haveid, // Did we get a msgid string? havestr; // Did we get a msgstr string? linenum = 0; id[0] = '\0'; str[0] = '\0'; haveid = 0; havestr = 0; which = 0; while (cupsFileGets(fp, line, sizeof(line))) { linenum ++; // Skip blank and comment lines... if (line[0] == '#' || !line[0]) continue; // Strip the trailing quote... if ((ptr = (char *)strrchr(line, '\"')) == NULL) { _cupsLangPrintf(stderr, _("ppdc: Expected quoted string on line %d of %s."), linenum, f); cupsFileClose(fp); return (-1); } *ptr = '\0'; // Find start of value... if ((ptr = strchr(line, '\"')) == NULL) { _cupsLangPrintf(stderr, _("ppdc: Expected quoted string on line %d of %s."), linenum, f); cupsFileClose(fp); return (-1); } ptr ++; // Unquote the text... char *sptr, *dptr; // Source/destination pointers for (sptr = ptr, dptr = ptr; *sptr;) { if (*sptr == '\\') { sptr ++; if (isdigit(*sptr)) { *dptr = 0; while (isdigit(*sptr)) { *dptr = *dptr * 8 + *sptr - '0'; sptr ++; } dptr ++; } else { if (*sptr == 'n') *dptr++ = '\n'; else if (*sptr == 'r') *dptr++ = '\r'; else if (*sptr == 't') *dptr++ = '\t'; else *dptr++ = *sptr; sptr ++; } } else *dptr++ = *sptr++; } *dptr = '\0'; // Create or add to a message... if (!strncmp(line, "msgid", 5)) { if (haveid && havestr) add_message(id, str); strlcpy(id, ptr, sizeof(id)); str[0] = '\0'; haveid = 1; havestr = 0; which = 1; } else if (!strncmp(line, "msgstr", 6)) { if (!haveid) { _cupsLangPrintf(stderr, _("ppdc: Need a msgid line before any " "translation strings on line %d of %s."), linenum, f); cupsFileClose(fp); return (-1); } strlcpy(str, ptr, sizeof(str)); havestr = 1; which = 2; } else if (line[0] == '\"' && which == 2) strlcat(str, ptr, sizeof(str)); else if (line[0] == '\"' && which == 1) strlcat(id, ptr, sizeof(id)); else { _cupsLangPrintf(stderr, _("ppdc: Unexpected text on line %d of %s."), linenum, f); cupsFileClose(fp); return (-1); } } if (haveid && havestr) add_message(id, str); } else goto unknown_load_format; /* * Close the file and return... */ cupsFileClose(fp); return (0); /* * Unknown format error... */ unknown_load_format: _cupsLangPrintf(stderr, _("ppdc: Unknown message catalog format for \"%s\"."), f); cupsFileClose(fp); return (-1); } // // 'ppdcCatalog::save_messages()' - Save the messages to a .po file. // int // O - 0 on success, -1 on error ppdcCatalog::save_messages( const char *f) // I - File to save to { cups_file_t *fp; // Message file ppdcMessage *m; // Current message char *ptr; // Pointer into string int utf16; // Output UTF-16 .strings file? int ch; // Current character // Open the file... if ((ptr = (char *)strrchr(f, '.')) == NULL) return (-1); if (!strcmp(ptr, ".gz")) fp = cupsFileOpen(f, "w9"); else fp = cupsFileOpen(f, "w"); if (!fp) return (-1); // For .strings files, write a BOM for big-endian output... utf16 = !strcmp(ptr, ".strings"); if (utf16) put_utf16(fp, 0xfeff); // Loop through all of the messages... for (m = (ppdcMessage *)messages->first(); m; m = (ppdcMessage *)messages->next()) { if (utf16) { put_utf16(fp, '\"'); ptr = m->id->value; while ((ch = get_utf8(ptr)) != 0) switch (ch) { case '\n' : put_utf16(fp, '\\'); put_utf16(fp, 'n'); break; case '\\' : put_utf16(fp, '\\'); put_utf16(fp, '\\'); break; case '\"' : put_utf16(fp, '\\'); put_utf16(fp, '\"'); break; default : put_utf16(fp, ch); break; } put_utf16(fp, '\"'); put_utf16(fp, ' '); put_utf16(fp, '='); put_utf16(fp, ' '); put_utf16(fp, '\"'); ptr = m->string->value; while ((ch = get_utf8(ptr)) != 0) switch (ch) { case '\n' : put_utf16(fp, '\\'); put_utf16(fp, 'n'); break; case '\\' : put_utf16(fp, '\\'); put_utf16(fp, '\\'); break; case '\"' : put_utf16(fp, '\\'); put_utf16(fp, '\"'); break; default : put_utf16(fp, ch); break; } put_utf16(fp, '\"'); put_utf16(fp, ';'); put_utf16(fp, '\n'); } else { cupsFilePuts(fp, "msgid \""); for (ptr = m->id->value; *ptr; ptr ++) switch (*ptr) { case '\n' : cupsFilePuts(fp, "\\n"); break; case '\\' : cupsFilePuts(fp, "\\\\"); break; case '\"' : cupsFilePuts(fp, "\\\""); break; default : cupsFilePutChar(fp, *ptr); break; } cupsFilePuts(fp, "\"\n"); cupsFilePuts(fp, "msgstr \""); for (ptr = m->string->value; *ptr; ptr ++) switch (*ptr) { case '\n' : cupsFilePuts(fp, "\\n"); break; case '\\' : cupsFilePuts(fp, "\\\\"); break; case '\"' : cupsFilePuts(fp, "\\\""); break; default : cupsFilePutChar(fp, *ptr); break; } cupsFilePuts(fp, "\"\n"); cupsFilePutChar(fp, '\n'); } } cupsFileClose(fp); return (0); } #if defined(__APPLE__) && defined(CUPS_BUNDLEDIR) // // 'apple_add_message()' - Add a message from a localization dictionary. // static void apple_add_message(CFStringRef key, // I - Localization key CFStringRef val, // I - Localized value ppdcCatalog *c) // I - Message catalog { char id[1024], // Message id str[1024]; // Localized message if (CFStringGetCString(key, id, sizeof(id), kCFStringEncodingUTF8) && CFStringGetCString(val, str, sizeof(str), kCFStringEncodingUTF8)) c->add_message(id, str); } #endif /* __APPLE__ && CUPS_BUNDLEDIR */ // // 'get_utf8()' - Get a UTF-8 character. // static int // O - Unicode character or 0 on EOF get_utf8(char *&ptr) // IO - Pointer to character { int ch; // Current character if ((ch = *ptr++ & 255) < 0xc0) return (ch); if ((ch & 0xe0) == 0xc0) { // Two-byte UTF-8... if ((*ptr & 0xc0) != 0x80) return (0); ch = ((ch & 0x1f) << 6) | (*ptr++ & 0x3f); } else if ((ch & 0xf0) == 0xe0) { // Three-byte UTF-8... if ((*ptr & 0xc0) != 0x80) return (0); ch = ((ch & 0x0f) << 6) | (*ptr++ & 0x3f); if ((*ptr & 0xc0) != 0x80) return (0); ch = (ch << 6) | (*ptr++ & 0x3f); } else if ((ch & 0xf8) == 0xf0) { // Four-byte UTF-8... if ((*ptr & 0xc0) != 0x80) return (0); ch = ((ch & 0x07) << 6) | (*ptr++ & 0x3f); if ((*ptr & 0xc0) != 0x80) return (0); ch = (ch << 6) | (*ptr++ & 0x3f); if ((*ptr & 0xc0) != 0x80) return (0); ch = (ch << 6) | (*ptr++ & 0x3f); } return (ch); } // // 'get_utf16()' - Get a UTF-16 character... // static int // O - Unicode character or 0 on EOF get_utf16(cups_file_t *fp, // I - File to read from ppdc_cs_t &cs) // IO - Character set of file { int ch; // Current character unsigned char buffer[3]; // Bytes if (cs == PPDC_CS_AUTO) { // Get byte-order-mark, if present... if (cupsFileRead(fp, (char *)buffer, 2) != 2) return (0); if (buffer[0] == 0xfe && buffer[1] == 0xff) { // Big-endian UTF-16... cs = PPDC_CS_UTF16BE; if (cupsFileRead(fp, (char *)buffer, 2) != 2) return (0); } else if (buffer[0] == 0xff && buffer[1] == 0xfe) { // Little-endian UTF-16... cs = PPDC_CS_UTF16LE; if (cupsFileRead(fp, (char *)buffer, 2) != 2) return (0); } else if (buffer[0] == 0x00 && buffer[1] != 0x00) { // No BOM, assume big-endian UTF-16... cs = PPDC_CS_UTF16BE; } else if (buffer[0] != 0x00 && buffer[1] == 0x00) { // No BOM, assume little-endian UTF-16... cs = PPDC_CS_UTF16LE; } else { // No BOM, assume UTF-8... cs = PPDC_CS_UTF8; cupsFileRewind(fp); } } else if (cs != PPDC_CS_UTF8) { if (cupsFileRead(fp, (char *)buffer, 2) != 2) return (0); } if (cs == PPDC_CS_UTF8) { // UTF-8 character... if ((ch = cupsFileGetChar(fp)) < 0) return (0); if ((ch & 0xe0) == 0xc0) { // Two-byte UTF-8... if (cupsFileRead(fp, (char *)buffer, 1) != 1) return (0); if ((buffer[0] & 0xc0) != 0x80) return (0); ch = ((ch & 0x1f) << 6) | (buffer[0] & 0x3f); } else if ((ch & 0xf0) == 0xe0) { // Three-byte UTF-8... if (cupsFileRead(fp, (char *)buffer, 2) != 2) return (0); if ((buffer[0] & 0xc0) != 0x80 || (buffer[1] & 0xc0) != 0x80) return (0); ch = ((((ch & 0x0f) << 6) | (buffer[0] & 0x3f)) << 6) | (buffer[1] & 0x3f); } else if ((ch & 0xf8) == 0xf0) { // Four-byte UTF-8... if (cupsFileRead(fp, (char *)buffer, 3) != 3) return (0); if ((buffer[0] & 0xc0) != 0x80 || (buffer[1] & 0xc0) != 0x80 || (buffer[2] & 0xc0) != 0x80) return (0); ch = ((((((ch & 0x07) << 6) | (buffer[0] & 0x3f)) << 6) | (buffer[1] & 0x3f)) << 6) | (buffer[2] & 0x3f); } } else { // UTF-16 character... if (cs == PPDC_CS_UTF16BE) ch = (buffer[0] << 8) | buffer[1]; else ch = (buffer[1] << 8) | buffer[0]; if (ch >= 0xd800 && ch <= 0xdbff) { // Handle multi-word encoding... int lch; if (cupsFileRead(fp, (char *)buffer, 2) != 2) return (0); if (cs == PPDC_CS_UTF16BE) lch = (buffer[0] << 8) | buffer[1]; else lch = (buffer[1] << 8) | buffer[0]; if (lch < 0xdc00 || lch >= 0xdfff) return (0); ch = (((ch & 0x3ff) << 10) | (lch & 0x3ff)) + 0x10000; } } return (ch); } // // 'put_utf8()' - Add a UTF-8 character to a string. // static int // O - 0 on success, -1 on failure put_utf8(int ch, // I - Unicode character char *&ptr, // IO - String pointer char *end) // I - End of buffer { if (ch < 0x80) { // One-byte ASCII... if (ptr >= end) return (-1); *ptr++ = (char)ch; } else if (ch < 0x800) { // Two-byte UTF-8... if ((ptr + 1) >= end) return (-1); *ptr++ = (char)(0xc0 | (ch >> 6)); *ptr++ = (char)(0x80 | (ch & 0x3f)); } else if (ch < 0x10000) { // Three-byte UTF-8... if ((ptr + 2) >= end) return (-1); *ptr++ = (char)(0xe0 | (ch >> 12)); *ptr++ = (char)(0x80 | ((ch >> 6) & 0x3f)); *ptr++ = (char)(0x80 | (ch & 0x3f)); } else { // Four-byte UTF-8... if ((ptr + 3) >= end) return (-1); *ptr++ = (char)(0xf0 | (ch >> 18)); *ptr++ = (char)(0x80 | ((ch >> 12) & 0x3f)); *ptr++ = (char)(0x80 | ((ch >> 6) & 0x3f)); *ptr++ = (char)(0x80 | (ch & 0x3f)); } return (0); } // // 'put_utf16()' - Write a UTF-16 character to a file. // static int // O - 0 on success, -1 on failure put_utf16(cups_file_t *fp, // I - File to write to int ch) // I - Unicode character { unsigned char buffer[4]; // Output buffer if (ch < 0x10000) { // One-word UTF-16 big-endian... buffer[0] = (unsigned char)(ch >> 8); buffer[1] = (unsigned char)ch; if (cupsFileWrite(fp, (char *)buffer, 2) == 2) return (0); } else { // Two-word UTF-16 big-endian... ch -= 0x10000; buffer[0] = (unsigned char)(0xd8 | (ch >> 18)); buffer[1] = (unsigned char)(ch >> 10); buffer[2] = (unsigned char)(0xdc | ((ch >> 8) & 0x03)); buffer[3] = (unsigned char)ch; if (cupsFileWrite(fp, (char *)buffer, 4) == 4) return (0); } return (-1); }