diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 09:51:24 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 09:51:24 +0000 |
commit | f7548d6d28c313cf80e6f3ef89aed16a19815df1 (patch) | |
tree | a3f6f2a3f247293bee59ecd28e8cd8ceb6ca064a /src/lib-fts/fts-language.c | |
parent | Initial commit. (diff) | |
download | dovecot-f7548d6d28c313cf80e6f3ef89aed16a19815df1.tar.xz dovecot-f7548d6d28c313cf80e6f3ef89aed16a19815df1.zip |
Adding upstream version 1:2.3.19.1+dfsg1.upstream/1%2.3.19.1+dfsg1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/lib-fts/fts-language.c')
-rw-r--r-- | src/lib-fts/fts-language.c | 368 |
1 files changed, 368 insertions, 0 deletions
diff --git a/src/lib-fts/fts-language.c b/src/lib-fts/fts-language.c new file mode 100644 index 0000000..7874d3a --- /dev/null +++ b/src/lib-fts/fts-language.c @@ -0,0 +1,368 @@ +/* Copyright (c) 2014-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "array.h" +#include "llist.h" +#include "fts-language.h" + + +#ifdef HAVE_LIBEXTTEXTCAT_TEXTCAT_H +# include <libexttextcat/textcat.h> +#elif defined (HAVE_FTS_EXTTEXTCAT) +# include <textcat.h> +#endif + +#ifndef TEXTCAT_RESULT_UNKNOWN /* old textcat.h has typos */ +# ifdef TEXTCAT_RESULT_UNKOWN +# define TEXTCAT_RESULT_UNKNOWN TEXTCAT_RESULT_UNKOWN +# endif +#endif + +#define DETECT_STR_MAX_LEN 200 + +struct fts_textcat { + int refcount; + void *handle; + char *config_path, *data_dir, *failed; +}; + +struct fts_language_list { + pool_t pool; + ARRAY_TYPE(fts_language) languages; + struct fts_textcat *textcat; + const char *textcat_config; + const char *textcat_datadir; +}; + +pool_t fts_languages_pool; +ARRAY_TYPE(fts_language) fts_languages; +#ifdef HAVE_FTS_EXTTEXTCAT +static struct fts_textcat *fts_textcat_cache = NULL; +#endif + +/* ISO 639-1 alpha 2 codes for languages */ +const struct fts_language fts_languages_builtin [] = { + { "da" }, /* Danish */ + { "de" }, /* German */ + { "en" }, /* English */ + { "es" }, /* Spanish */ + { "fi" }, /* Finnish */ + { "fr" }, /* French */ + { "it" }, /* Italian */ + { "nl" }, /* Dutch */ + { "no" }, /* Both Bokmal and Nynorsk are detected as Norwegian */ + { "pt" }, /* Portuguese */ + { "ro" }, /* Romanian */ + { "ru" }, /* Russian */ + { "sv" }, /* Swedish */ + { "tr" }, /* Turkish */ +}; + +const struct fts_language fts_language_data = { + "data" +}; + +#ifdef HAVE_FTS_EXTTEXTCAT +static void fts_textcat_unref(struct fts_textcat *textcat) +{ + i_assert(textcat->refcount > 0); + if (--textcat->refcount > 0) + return; + + if (textcat == fts_textcat_cache) + fts_textcat_cache = NULL; + + i_free(textcat->config_path); + i_free(textcat->data_dir); + i_free(textcat->failed); + if (textcat->handle != NULL) + textcat_Done(textcat->handle); + i_free(textcat); +} +#endif + +void fts_languages_init(void) +{ + unsigned int i; + const struct fts_language *lp; + + fts_languages_pool = pool_alloconly_create("fts_language", + sizeof(fts_languages_builtin)); + p_array_init(&fts_languages, fts_languages_pool, + N_ELEMENTS(fts_languages_builtin)); + for (i = 0; i < N_ELEMENTS(fts_languages_builtin); i++){ + lp = &fts_languages_builtin[i]; + array_push_back(&fts_languages, &lp); + } +} + +void fts_languages_deinit(void) +{ +#ifdef HAVE_FTS_EXTTEXTCAT + if (fts_textcat_cache != NULL) + fts_textcat_unref(fts_textcat_cache); +#endif + pool_unref(&fts_languages_pool); +} + +void fts_language_register(const char *name) +{ + struct fts_language *lang; + + if (fts_language_find(name) != NULL) + return; + + lang = p_new(fts_languages_pool, struct fts_language, 1); + lang->name = p_strdup(fts_languages_pool, name); + array_push_back(&fts_languages, (const struct fts_language **)&lang); +} + +const struct fts_language *fts_language_find(const char *name) +{ + const struct fts_language *lang; + + array_foreach_elem(&fts_languages, lang) { + if (strcmp(lang->name, name) == 0) + return lang; + } + return NULL; +} + +int fts_language_list_init(const char *const *settings, + struct fts_language_list **list_r, + const char **error_r) +{ + struct fts_language_list *lp; + pool_t pool; + unsigned int i; + const char *conf = NULL, *data = NULL; + + for (i = 0; settings[i] != NULL; i += 2) { + const char *key = settings[i], *value = settings[i+1]; + + if (strcmp(key, "fts_language_config") == 0) + conf = value; + else if (strcmp(key, "fts_language_data") == 0) + data = value; + else { + *error_r = t_strdup_printf("Unknown setting: %s", key); + return -1; + } + } + + pool = pool_alloconly_create("fts_language_list", 128); + lp = p_new(pool, struct fts_language_list, 1); + lp->pool = pool; + if (conf != NULL) + lp->textcat_config = p_strdup(pool, conf); + else + lp->textcat_config = NULL; + if (data != NULL) + lp->textcat_datadir = p_strdup(pool, data); + else + lp->textcat_datadir = NULL; + p_array_init(&lp->languages, pool, 32); + *list_r = lp; + return 0; +} + +void fts_language_list_deinit(struct fts_language_list **list) +{ + struct fts_language_list *lp = *list; + + *list = NULL; +#ifdef HAVE_FTS_EXTTEXTCAT + if (lp->textcat != NULL) + fts_textcat_unref(lp->textcat); +#endif + pool_unref(&lp->pool); +} + +static const struct fts_language * +fts_language_list_find(struct fts_language_list *list, const char *name) +{ + const struct fts_language *lang; + + array_foreach_elem(&list->languages, lang) { + if (strcmp(lang->name, name) == 0) + return lang; + } + return NULL; +} + +void fts_language_list_add(struct fts_language_list *list, + const struct fts_language *lang) +{ + i_assert(fts_language_list_find(list, lang->name) == NULL); + array_push_back(&list->languages, &lang); +} + +bool fts_language_list_add_names(struct fts_language_list *list, + const char *names, + const char **unknown_name_r) +{ + const char *const *langs; + const struct fts_language *lang; + + for (langs = t_strsplit_spaces(names, ", "); *langs != NULL; langs++) { + lang = fts_language_find(*langs); + if (lang == NULL) { + /* unknown language */ + *unknown_name_r = *langs; + return FALSE; + } + if (fts_language_list_find(list, lang->name) == NULL) + fts_language_list_add(list, lang); + } + return TRUE; +} + +const ARRAY_TYPE(fts_language) * +fts_language_list_get_all(struct fts_language_list *list) +{ + return &list->languages; +} + +const struct fts_language * +fts_language_list_get_first(struct fts_language_list *list) +{ + const struct fts_language *const *langp; + + langp = array_front(&list->languages); + return *langp; +} + +#ifdef HAVE_FTS_EXTTEXTCAT +static bool fts_language_match_lists(struct fts_language_list *list, + candidate_t *candp, int candp_len, + const struct fts_language **lang_r) +{ + const char *name; + + for (int i = 0; i < candp_len; i++) { + /* name is <lang>-<optional country or characterset>-<encoding> + eg, fi--utf8 or pt-PT-utf8 */ + name = t_strcut(candp[i].name, '-'); + + /* For Norwegian we treat both bokmal and nynorsk as "no". */ + if (strcmp(name, "nb") == 0 || strcmp(name, "nn") == 0) + name = "no"; + if ((*lang_r = fts_language_list_find(list, name)) != NULL) + return TRUE; + } + return FALSE; +} +#endif + +#ifdef HAVE_FTS_EXTTEXTCAT +static int fts_language_textcat_init(struct fts_language_list *list, + const char **error_r) +{ + const char *config_path; + const char *data_dir; + + if (list->textcat != NULL) { + if (list->textcat->failed != NULL) { + *error_r = list->textcat->failed; + return -1; + } + i_assert(list->textcat->handle != NULL); + return 0; + } + + config_path = list->textcat_config != NULL ? list->textcat_config : + TEXTCAT_DATADIR"/fpdb.conf"; + data_dir = list->textcat_datadir != NULL ? list->textcat_datadir : + TEXTCAT_DATADIR"/"; + if (fts_textcat_cache != NULL) { + if (strcmp(fts_textcat_cache->config_path, config_path) == 0 && + strcmp(fts_textcat_cache->data_dir, data_dir) == 0) { + list->textcat = fts_textcat_cache; + list->textcat->refcount++; + return 0; + } + fts_textcat_unref(fts_textcat_cache); + } + + fts_textcat_cache = list->textcat = i_new(struct fts_textcat, 1); + fts_textcat_cache->refcount = 2; + fts_textcat_cache->config_path = i_strdup(config_path); + fts_textcat_cache->data_dir = i_strdup(data_dir); + fts_textcat_cache->handle = special_textcat_Init(config_path, data_dir); + if (fts_textcat_cache->handle == NULL) { + fts_textcat_cache->failed = i_strdup_printf( + "special_textcat_Init(%s, %s) failed", + config_path, data_dir); + *error_r = fts_textcat_cache->failed; + return -1; + } + /* The textcat minimum document size could be set here. It + currently defaults to 3. UTF8 is enabled by default. */ + return 0; +} +#endif + +static enum fts_language_result +fts_language_detect_textcat(struct fts_language_list *list ATTR_UNUSED, + const unsigned char *text ATTR_UNUSED, + size_t size ATTR_UNUSED, + const struct fts_language **lang_r ATTR_UNUSED, + const char **error_r ATTR_UNUSED) +{ +#ifdef HAVE_FTS_EXTTEXTCAT + candidate_t *candp; /* textcat candidate result array pointer */ + int cnt; + bool match = FALSE; + + if (fts_language_textcat_init(list, error_r) < 0) + return FTS_LANGUAGE_RESULT_ERROR; + + candp = textcat_GetClassifyFullOutput(list->textcat->handle); + if (candp == NULL) + i_fatal_status(FATAL_OUTOFMEM, "textcat_GetCLassifyFullOutput failed: malloc() returned NULL"); + cnt = textcat_ClassifyFull(list->textcat->handle, (const void *)text, + I_MIN(size, DETECT_STR_MAX_LEN), candp); + if (cnt > 0) { + T_BEGIN { + match = fts_language_match_lists(list, candp, cnt, lang_r); + } T_END; + textcat_ReleaseClassifyFullOutput(list->textcat->handle, candp); + if (match) + return FTS_LANGUAGE_RESULT_OK; + else + return FTS_LANGUAGE_RESULT_UNKNOWN; + } else { + textcat_ReleaseClassifyFullOutput(list->textcat->handle, candp); + switch (cnt) { + case TEXTCAT_RESULT_SHORT: + i_assert(size < DETECT_STR_MAX_LEN); + return FTS_LANGUAGE_RESULT_SHORT; + case TEXTCAT_RESULT_UNKNOWN: + return FTS_LANGUAGE_RESULT_UNKNOWN; + default: + i_unreached(); + } + } +#else + return FTS_LANGUAGE_RESULT_UNKNOWN; +#endif +} + +enum fts_language_result +fts_language_detect(struct fts_language_list *list, + const unsigned char *text ATTR_UNUSED, + size_t size ATTR_UNUSED, + const struct fts_language **lang_r, + const char **error_r) +{ + i_assert(array_count(&list->languages) > 0); + + /* if there's only a single wanted language, return it always. */ + if (array_count(&list->languages) == 1) { + const struct fts_language *const *langp = + array_front(&list->languages); + *lang_r = *langp; + return FTS_LANGUAGE_RESULT_OK; + } + return fts_language_detect_textcat(list, text, size, lang_r, error_r); +} |