/* * BRLTTY - A background process providing access to the console screen (when in * text mode) for a blind person using a refreshable braille display. * * Copyright (C) 1995-2021 by The BRLTTY Developers. * * BRLTTY comes with ABSOLUTELY NO WARRANTY. * * This is free software, placed under the terms of the * GNU Lesser General Public License, as published by the Free Software * Foundation; either version 2.1 of the License, or (at your option) any * later version. Please see the file LICENSE-LGPL for details. * * Web Page: http://brltty.app/ * * This software is maintained by Dave Mielke . */ #include "prologue.h" #include #include #include #include #include #include "log.h" #include "messages.h" #include "file.h" // MinGW doesn't define LC_MESSAGES #ifndef LC_MESSAGES #define LC_MESSAGES LC_ALL #endif /* LC_MESSAGES */ // Windows needs O_BINARY #ifndef O_BINARY #define O_BINARY 0 #endif /* O_BINARY */ static char *localeDirectory = NULL; static char *localeSpecifier = NULL; static char *domainName = NULL; const char * getMessagesDirectory (void) { return localeDirectory; } const char * getMessagesLocale (void) { return localeSpecifier; } const char * getMessagesDomain (void) { return domainName; } static const uint32_t magicNumber = UINT32_C(0X950412DE); typedef uint32_t GetIntegerFunction (uint32_t value); typedef struct { uint32_t magicNumber; uint32_t versionNumber; uint32_t messageCount; uint32_t originalMessages; uint32_t translatedMessages; uint32_t hashSize; uint32_t hashOffset; } MessagesHeader; typedef struct { union { void *area; const unsigned char *bytes; const MessagesHeader *header; } view; size_t areaSize; GetIntegerFunction *getInteger; } MessagesData; static MessagesData messagesData = { .view.area = NULL, .areaSize = 0, .getInteger = NULL, }; static uint32_t getNativeInteger (uint32_t value) { return value; } static uint32_t getFlippedInteger (uint32_t value) { uint32_t result = 0; while (value) { result <<= 8; result |= value & UINT8_MAX; value >>= 8; } return result; } static int checkMagicNumber (MessagesData *data) { const MessagesHeader *header = data->view.header; { static GetIntegerFunction *const functions[] = { getNativeInteger, getFlippedInteger, NULL }; GetIntegerFunction *const *function = functions; while (*function) { if ((*function)(header->magicNumber) == magicNumber) { data->getInteger = *function; return 1; } function += 1; } } return 0; } static char * makeLocaleDirectoryPath (void) { size_t length = strlen(localeSpecifier); char dialect[length + 1]; strcpy(dialect, localeSpecifier); length = strcspn(dialect, ".@"); dialect[length] = 0; char language[length + 1]; strcpy(language, dialect); length = strcspn(language, "_"); language[length] = 0; char *codes[] = {dialect, language, NULL}; char **code = codes; while (*code && **code) { char *path = makePath(localeDirectory, *code); if (path) { if (testDirectoryPath(path)) return path; free(path); } code += 1; } logMessage(LOG_WARNING, "messages locale not found: %s", localeSpecifier); return NULL; } static char * makeMessagesFilePath (void) { char *locale = makeLocaleDirectoryPath(); if (locale) { char *category = makePath(locale, "LC_MESSAGES"); free(locale); locale = NULL; if (category) { char *file = makeFilePath(category, domainName, ".mo"); free(category); category = NULL; if (file) return file; } } return NULL; } int loadMessagesData (void) { if (messagesData.view.area) return 1; ensureAllMessagesProperties(); int loaded = 0; char *path = makeMessagesFilePath(); if (path) { int fd = open(path, (O_RDONLY | O_BINARY)); if (fd != -1) { struct stat info; if (fstat(fd, &info) != -1) { size_t size = info.st_size; void *area = NULL; if (size) { if ((area = malloc(size))) { ssize_t count = read(fd, area, size); if (count == -1) { logMessage(LOG_WARNING, "messages data read error: %s: %s", path, strerror(errno) ); } else if (count < size) { logMessage(LOG_WARNING, "truncated messages data: %"PRIssize" < %"PRIsize": %s", count, size, path ); } else { MessagesData data = { .view.area = area, .areaSize = size }; if (checkMagicNumber(&data)) { messagesData = data; area = NULL; loaded = 1; } } if (!loaded) free(area); } else { logMallocError(); } } else { logMessage(LOG_WARNING, "no messages data"); } } else { logMessage(LOG_WARNING, "messages file stat error: %s: %s", path, strerror(errno) ); } close(fd); } else { logMessage(LOG_WARNING, "messages file open error: %s: %s", path, strerror(errno) ); } free(path); } return loaded; } void releaseMessagesData (void) { if (messagesData.view.area) free(messagesData.view.area); memset(&messagesData, 0, sizeof(messagesData)); } static inline const MessagesHeader * getHeader (void) { return messagesData.view.header; } static inline const void * getItem (uint32_t offset) { return &messagesData.view.bytes[messagesData.getInteger(offset)]; } uint32_t getMessageCount (void) { return messagesData.getInteger(getHeader()->messageCount); } struct MessageStruct { uint32_t length; uint32_t offset; }; uint32_t getMessageLength (const Message *message) { return messagesData.getInteger(message->length); } const char * getMessageText (const Message *message) { return getItem(message->offset); } static inline const Message * getOriginalMessages (void) { return getItem(getHeader()->originalMessages); } static inline const Message * getTranslatedMessages (void) { return getItem(getHeader()->translatedMessages); } const Message * getOriginalMessage (unsigned int index) { return &getOriginalMessages()[index]; } const Message * getTranslatedMessage (unsigned int index) { return &getTranslatedMessages()[index]; } const char * getMessagesMetadata (void) { if (getMessageCount() == 0) return ""; const Message *original = getOriginalMessage(0); if (getMessageLength(original) != 0) return ""; return getMessageText(getTranslatedMessage(0)); } char * getMessagesProperty (const char *name) { size_t nameLength = strlen(name); const char *metadata = getMessagesMetadata(); while (metadata) { const char *line = metadata; size_t lineLength = strcspn(line, "\n\x00"); const char *end = line + lineLength; metadata = *end? (line + lineLength + 1): NULL; if (nameLength < lineLength) { if (memcmp(line, name, nameLength) == 0) { if (line[nameLength] == ':') { const char *value = line + nameLength + 1; while (iswspace(*value)) value += 1; size_t valueLength = end - value; char *copy = malloc(valueLength + 1); if (copy) { memcpy(copy, value, valueLength); copy[valueLength] = 0; } else { logMallocError(); } return copy; } } } } return NULL; } char * getMessagesAttribute (const char *property, const char *name) { size_t nameLength = strlen(name); const char *byte = property; while (*byte) { while (iswspace(*byte)) byte += 1; const char *nameStart = byte; while (iswalpha(*byte)) byte += 1; const char *nameEnd = byte; while (iswspace(*byte)) byte += 1; if (!*byte) break; if (*byte != '=') continue; byte += 1; while (iswspace(*byte)) byte += 1; const char *valueStart = byte; while (*byte && (*byte != ';')) byte += 1; const char *valueEnd = byte; if (*byte) byte += 1; if ((nameEnd - nameStart) == nameLength) { if (memcmp(name, nameStart, nameLength) == 0) { size_t length = valueEnd - valueStart; char *value = malloc(length + 1); if (value) { memcpy(value, valueStart, length); value[length] = 0; } else { logMallocError(); } return value; } } } return NULL; } int findOriginalMessage (const char *text, size_t textLength, unsigned int *index) { const Message *messages = getOriginalMessages(); int from = 0; int to = getMessageCount(); while (from < to) { int current = (from + to) / 2; const Message *message = &messages[current]; uint32_t messageLength = getMessageLength(message); int relation = memcmp(text, getMessageText(message), MIN(textLength, messageLength)); if (relation == 0) { if (textLength == messageLength) { *index = current; return 1; } relation = (textLength < messageLength)? -1: 1; } if (relation < 0) { to = current; } else { from = current + 1; } } return 0; } const Message * findSimpleTranslation (const char *text, size_t length) { if (!text) return NULL; if (!length) return NULL; if (loadMessagesData()) { unsigned int index; if (findOriginalMessage(text, length, &index)) { return getTranslatedMessage(index); } } return NULL; } const char * getSimpleTranslation (const char *text) { const Message *translation = findSimpleTranslation(text, strlen(text)); if (translation) return getMessageText(translation); return text; } const Message * findPluralTranslation (const char *const *strings) { unsigned int count = 0; while (strings[count]) count += 1; if (!count) return NULL; size_t size = 0; size_t lengths[count]; for (unsigned int index=0; index