1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
2  *
3  * Copyright 2008  Red Hat, Inc,
4  *           2007  William Jon McCann <mccann@jhu.edu>
5  *
6  * This program is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA..
19  *
20  * Written by : William Jon McCann <mccann@jhu.edu>
21  *              Ray Strode <rstrode@redhat.com>
22  */
23 
24 #include "config.h"
25 #include "common.h"
26 
27 #include <stdlib.h>
28 #include <stdio.h>
29 #include <unistd.h>
30 #include <string.h>
31 #include <errno.h>
32 #include <dirent.h>
33 #include <locale.h>
34 #include <langinfo.h>
35 #include <sys/stat.h>
36 
37 #include <glib.h>
38 
39 #include "gdm-languages.h"
40 
41 #include <langinfo.h>
42 #ifndef __LC_LAST
43 #define __LC_LAST       13
44 #endif
45 #include "locarchive.h"
46 
47 #define ALIASES_FILE DATADIR "/gdm/locale.alias"
48 #define ARCHIVE_FILE LIBLOCALEDIR "/locale-archive"
49 #define ISO_CODES_DATADIR ISO_CODES_PREFIX "/share/xml/iso-codes"
50 #define ISO_CODES_LOCALESDIR ISO_CODES_PREFIX "/share/locale"
51 
52 typedef struct _GdmLocale {
53     char *id;
54     char *name;
55     char *language_code;
56     char *territory_code;
57     char *codeset;
58     char *modifier;
59 } GdmLocale;
60 
61 static GHashTable *gdm_languages_map;
62 static GHashTable *gdm_territories_map;
63 static GHashTable *gdm_available_locales_map;
64 
65 static char * construct_language_name(const char *language,
66                                       const char *territory,
67                                       const char *codeset,
68                                       const char *modifier);
69 
70 static gboolean language_name_is_valid(const char *language_name);
71 
72 static void
gdm_locale_free(GdmLocale * locale)73 gdm_locale_free(GdmLocale *locale)
74 {
75     if (locale == NULL) {
76         return;
77     }
78 
79     g_free(locale->id);
80     g_free(locale->name);
81     g_free(locale->codeset);
82     g_free(locale->modifier);
83     g_free(locale);
84 }
85 
86 static char *
normalize_codeset(const char * codeset)87 normalize_codeset(const char *codeset)
88 {
89     char *normalized_codeset;
90     const char *p;
91     char *q;
92 
93     normalized_codeset = g_strdup(codeset);
94 
95     if (codeset != NULL) {
96         for (p = codeset, q = normalized_codeset;
97                 *p != '\0'; p++) {
98 
99             if (*p == '-' || *p == '_') {
100                 continue;
101             }
102 
103             *q = g_ascii_tolower(*p);
104             q++;
105         }
106         *q = '\0';
107     }
108 
109     return normalized_codeset;
110 }
111 
112 /*
113  * According to http://en.wikipedia.org/wiki/Locale
114  * locale names are of the form:
115  * [language[_territory][.codeset][@modifier]]
116  */
117 void
gdm_parse_language_name(const char * name,char ** language_codep,char ** territory_codep,char ** codesetp,char ** modifierp)118 gdm_parse_language_name(const char *name,
119                         char      **language_codep,
120                         char      **territory_codep,
121                         char      **codesetp,
122                         char      **modifierp)
123 {
124     GRegex     *re;
125     GMatchInfo *match_info;
126     gboolean    res;
127     GError     *error;
128     gchar      *normalized_codeset = NULL;
129     gchar      *normalized_name = NULL;
130 
131     match_info = NULL;
132 
133     error = NULL;
134     re = g_regex_new("^(?P<language>[^_.@[:space:]]+)"
135                      "(_(?P<territory>[[:upper:]]+))?"
136                      "(\\.(?P<codeset>[-_0-9a-zA-Z]+))?"
137                      "(@(?P<modifier>[[:ascii:]]+))?$",
138                      0, 0, &error);
139     if (re == NULL) {
140         g_warning("%s", error->message);
141         goto out;
142     }
143 
144     if (!g_regex_match(re, name, 0, &match_info) ||
145             g_match_info_is_partial_match(match_info)) {
146         g_warning("locale %s isn't valid\n", name);
147         goto out;
148     }
149 
150     res = g_match_info_matches(match_info);
151     if (! res) {
152         g_warning("Unable to parse locale: %s", name);
153         goto out;
154     }
155 
156     if (language_codep != NULL) {
157         *language_codep = g_match_info_fetch_named(match_info, "language");
158     }
159 
160     if (territory_codep != NULL) {
161         *territory_codep = g_match_info_fetch_named(match_info, "territory");
162 
163         if (*territory_codep != NULL &&
164                 *territory_codep[0] == '\0') {
165             g_free(*territory_codep);
166             *territory_codep = NULL;
167         }
168     }
169 
170     if (codesetp != NULL) {
171         *codesetp = g_match_info_fetch_named(match_info, "codeset");
172 
173         if (*codesetp != NULL &&
174                 *codesetp[0] == '\0') {
175             g_free(*codesetp);
176             *codesetp = NULL;
177         }
178     }
179 
180     if (modifierp != NULL) {
181         *modifierp = g_match_info_fetch_named(match_info, "modifier");
182 
183         if (*modifierp != NULL &&
184                 *modifierp[0] == '\0') {
185             g_free(*modifierp);
186             *modifierp = NULL;
187         }
188     }
189 
190     if (codesetp != NULL && *codesetp != NULL) {
191         normalized_codeset = normalize_codeset(*codesetp);
192         normalized_name = construct_language_name(language_codep ? *language_codep : NULL,
193                           territory_codep ? *territory_codep : NULL,
194                           normalized_codeset,
195                           modifierp ? *modifierp : NULL);
196 
197         if (language_name_is_valid(normalized_name)) {
198             g_free(*codesetp);
199             *codesetp = normalized_codeset;
200         } else {
201             g_free(normalized_codeset);
202         }
203         g_free(normalized_name);
204     }
205 
206 out:
207     g_match_info_free(match_info);
208     g_regex_unref(re);
209 }
210 
211 static char *
construct_language_name(const char * language,const char * territory,const char * codeset,const char * modifier)212 construct_language_name(const char *language,
213                         const char *territory,
214                         const char *codeset,
215                         const char *modifier)
216 {
217     char *name;
218 
219     g_assert(language[0] != 0);
220     g_assert(territory == NULL || territory[0] != 0);
221     g_assert(codeset == NULL || codeset[0] != 0);
222     g_assert(modifier == NULL || modifier[0] != 0);
223 
224     name = g_strdup_printf("%s%s%s%s%s%s%s",
225                            language,
226                            territory != NULL ? "_" : "",
227                            territory != NULL ? territory : "",
228                            codeset != NULL ? "." : "",
229                            codeset != NULL ? codeset : "",
230                            modifier != NULL ? "@" : "",
231                            modifier != NULL ? modifier : "");
232 
233     return name;
234 }
235 
236 char *
gdm_normalize_language_name(const char * name)237 gdm_normalize_language_name(const char *name)
238 {
239     char *normalized_name;
240     char *language_code;
241     char *territory_code;
242     char *codeset;
243     char *modifier;
244 
245     if (name[0] == '\0') {
246         return NULL;
247     }
248 
249     gdm_parse_language_name(name,
250                             &language_code,
251                             &territory_code,
252                             &codeset, &modifier);
253 
254     normalized_name = construct_language_name(language_code,
255                       territory_code,
256                       codeset, modifier);
257     g_free(language_code);
258     g_free(territory_code);
259     g_free(codeset);
260     g_free(modifier);
261 
262     return normalized_name;
263 }
264 
265 static gboolean
language_name_is_valid(const char * language_name)266 language_name_is_valid(const char *language_name)
267 {
268     char     *old_locale;
269     gboolean  is_valid;
270 #ifdef WITH_INCOMPLETE_LOCALES
271     int lc_type_id = LC_CTYPE;
272 #else
273     int lc_type_id = LC_MESSAGES;
274 #endif
275 
276     old_locale = g_strdup(setlocale(lc_type_id, NULL));
277     is_valid = setlocale(lc_type_id, language_name) != NULL;
278     setlocale(lc_type_id, old_locale);
279     g_free(old_locale);
280 
281     return is_valid;
282 }
283 
284 static void
language_name_get_codeset_details(const char * language_name,char ** pcodeset,gboolean * is_utf8)285 language_name_get_codeset_details(const char  *language_name,
286                                   char       **pcodeset,
287                                   gboolean    *is_utf8)
288 {
289     char     *old_locale;
290     char     *codeset;
291 
292     old_locale = g_strdup(setlocale(LC_CTYPE, NULL));
293 
294     if (setlocale(LC_CTYPE, language_name) == NULL) {
295         g_free(old_locale);
296         return;
297     }
298 
299     codeset = nl_langinfo(CODESET);
300 
301     if (pcodeset != NULL) {
302         *pcodeset = g_strdup(codeset);
303     }
304 
305     if (is_utf8 != NULL) {
306         codeset = normalize_codeset(codeset);
307 
308         *is_utf8 = strcmp(codeset, "utf8") == 0;
309         g_free(codeset);
310     }
311 
312     setlocale(LC_CTYPE, old_locale);
313     g_free(old_locale);
314 }
315 
316 static gboolean
language_name_has_translations(const char * language_name)317 language_name_has_translations(const char *language_name)
318 {
319     GDir        *dir;
320     char        *path;
321     const char  *name;
322     gboolean     has_translations;
323 
324     path = g_build_filename(LOCALEDIR, language_name, "LC_MESSAGES", NULL);
325 
326     has_translations = FALSE;
327     dir = g_dir_open(path, 0, NULL);
328     g_free(path);
329 
330     if (dir == NULL) {
331         goto out;
332     }
333 
334     do {
335         name = g_dir_read_name(dir);
336 
337         if (name == NULL) {
338             break;
339         }
340 
341         if (g_str_has_suffix(name, ".mo")) {
342             has_translations = TRUE;
343             break;
344         }
345     } while (name != NULL);
346     g_dir_close(dir);
347 
348 out:
349     return has_translations;
350 }
351 
352 static gboolean
add_locale(const char * language_name,gboolean utf8_only)353 add_locale(const char *language_name,
354            gboolean    utf8_only)
355 {
356     GdmLocale *locale;
357     GdmLocale *old_locale;
358     char      *name;
359     gboolean   is_utf8 = TRUE;
360 
361     g_return_val_if_fail(language_name != NULL, FALSE);
362 
363     language_name_get_codeset_details(language_name, NULL, &is_utf8);
364 
365     if (is_utf8) {
366         name = g_strdup(language_name);
367     } else if (utf8_only) {
368         name = g_strdup_printf("%s.utf8", language_name);
369 
370         language_name_get_codeset_details(name, NULL, &is_utf8);
371         if (is_utf8) {
372             g_free(name);
373             return FALSE;
374         }
375     } else {
376         name = g_strdup(language_name);
377     }
378 
379     if (!language_name_is_valid(name)) {
380         g_warning("Your locale '%s' was failed by setlocale()", name);
381         g_free(name);
382         return FALSE;
383     }
384 
385     locale = g_new0(GdmLocale, 1);
386     gdm_parse_language_name(name,
387                             &locale->language_code,
388                             &locale->territory_code,
389                             &locale->codeset,
390                             &locale->modifier);
391     g_free(name);
392     name = NULL;
393 
394 #ifdef WITH_INCOMPLETE_LOCALES
395     if (utf8_only) {
396         if (locale->territory_code == NULL || locale->modifier) {
397             gdm_locale_free(locale);
398             return FALSE;
399         }
400     }
401 #endif
402 
403     locale->id = construct_language_name(locale->language_code, locale->territory_code,
404                                          NULL, locale->modifier);
405     locale->name = construct_language_name(locale->language_code, locale->territory_code,
406                                            locale->codeset, locale->modifier);
407 
408 #ifndef WITH_INCOMPLETE_LOCALES
409     if (!language_name_has_translations(locale->name) &&
410             !language_name_has_translations(locale->id) &&
411             !language_name_has_translations(locale->language_code) &&
412             utf8_only) {
413         g_warning("Your locale '%s' doesn't have message catalog files",
414                   language_name);
415         gdm_locale_free(locale);
416         return FALSE;
417     }
418 #endif
419 
420     if (!utf8_only) {
421         g_free(locale->id);
422         locale->id = g_strdup(locale->name);
423     }
424 
425     old_locale = g_hash_table_lookup(gdm_available_locales_map, locale->id);
426     if (old_locale != NULL) {
427         if (strlen(old_locale->name) > strlen(locale->name)) {
428             gdm_locale_free(locale);
429             return FALSE;
430         }
431     }
432 
433     g_hash_table_insert(gdm_available_locales_map, g_strdup(locale->id), locale);
434 
435     return TRUE;
436 }
437 
438 struct nameent {
439     char    *name;
440     uint32_t locrec_offset;
441 };
442 
443 static gboolean
collect_locales_from_archive(void)444 collect_locales_from_archive(void)
445 {
446     GMappedFile        *mapped;
447     GError             *error;
448     char               *addr;
449     struct locarhead   *head;
450     struct namehashent *namehashtab;
451     struct nameent     *names;
452     uint32_t            used;
453     uint32_t            cnt;
454     gsize               len;
455     gboolean            locales_collected;
456 
457     error = NULL;
458     mapped = g_mapped_file_new(ARCHIVE_FILE, FALSE, &error);
459     if (mapped == NULL) {
460         g_warning("Mapping failed for %s: %s", ARCHIVE_FILE, error->message);
461         g_error_free(error);
462         return FALSE;
463     }
464 
465     locales_collected = FALSE;
466 
467     addr = g_mapped_file_get_contents(mapped);
468     len = g_mapped_file_get_length(mapped);
469 
470     head = (struct locarhead *) addr;
471     if (head->namehash_offset + head->namehash_size > len
472             || head->string_offset + head->string_size > len
473             || head->locrectab_offset + head->locrectab_size > len
474             || head->sumhash_offset + head->sumhash_size > len) {
475         goto out;
476     }
477 
478     namehashtab = (struct namehashent *)(addr + head->namehash_offset);
479 
480     names = (struct nameent *) g_new0(struct nameent, head->namehash_used);
481     for (cnt = used = 0; cnt < head->namehash_size; ++cnt) {
482         if (namehashtab[cnt].locrec_offset != 0) {
483             names[used].name = addr + namehashtab[cnt].name_offset;
484             names[used++].locrec_offset = namehashtab[cnt].locrec_offset;
485         }
486     }
487 
488     for (cnt = 0; cnt < used; ++cnt) {
489         add_locale(names[cnt].name, TRUE);
490     }
491 
492     g_free(names);
493 
494     locales_collected = TRUE;
495 out:
496 
497     g_mapped_file_unref(mapped);
498     return locales_collected;
499 }
500 
501 static int
select_dirs(const struct dirent * dirent)502 select_dirs(const struct dirent *dirent)
503 {
504     int result = 0;
505 
506     if (strcmp(dirent->d_name, ".") != 0 && strcmp(dirent->d_name, "..") != 0) {
507         mode_t mode = 0;
508 
509 #if defined(_DIRENT_HAVE_D_TYPE) && !defined(__DragonFly__)
510         if (dirent->d_type != DT_UNKNOWN && dirent->d_type != DT_LNK) {
511             mode = DTTOIF(dirent->d_type);
512         } else
513 #endif
514         {
515             struct stat st;
516             char       *path;
517 
518             path = g_build_filename(LIBLOCALEDIR, dirent->d_name, NULL);
519             if (stat(path, &st) == 0) {
520                 mode = st.st_mode;
521             }
522             g_free(path);
523         }
524 
525         result = S_ISDIR(mode);
526     }
527 
528     return result;
529 }
530 
531 static void
collect_locales_from_directory(void)532 collect_locales_from_directory(void)
533 {
534     struct dirent **dirents;
535     int             ndirents;
536     int             cnt;
537 
538     ndirents = scandir(LIBLOCALEDIR, &dirents, select_dirs, alphasort);
539 
540     for (cnt = 0; cnt < ndirents; ++cnt) {
541         add_locale(dirents[cnt]->d_name, TRUE);
542     }
543 
544     if (ndirents > 0) {
545         free(dirents);
546     }
547 }
548 
549 static void
collect_locales_from_locale_file(const char * locale_file)550 collect_locales_from_locale_file(const char *locale_file)
551 {
552     FILE *langlist;
553     char curline[256];
554     char *getsret;
555 
556     if (locale_file == NULL)
557         return;
558 
559     langlist = fopen(locale_file, "r");
560 
561     if (langlist == NULL)
562         return;
563 
564     for (;;) {
565         char *name;
566         char *lang;
567         char **lang_list;
568         int i;
569 
570         getsret = fgets(curline, sizeof(curline), langlist);
571         if (getsret == NULL)
572             break;
573 
574         if (curline[0] <= ' ' ||
575                 curline[0] == '#')
576             continue;
577 
578         name = strtok(curline, " \t\r\n");
579         if (name == NULL)
580             continue;
581 
582         lang = strtok(NULL, " \t\r\n");
583         if (lang == NULL)
584             continue;
585 
586         lang_list = g_strsplit(lang, ",", -1);
587         if (lang_list == NULL)
588             continue;
589 
590         lang = NULL;
591         for (i = 0; lang_list[i] != NULL; i++) {
592             if (add_locale(lang_list[i], FALSE)) {
593                 break;
594             }
595         }
596         g_strfreev(lang_list);
597     }
598 
599     fclose(langlist);
600 }
601 
602 static void
collect_locales(void)603 collect_locales(void)
604 {
605 
606     if (gdm_available_locales_map == NULL) {
607         gdm_available_locales_map = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, (GDestroyNotify) gdm_locale_free);
608     }
609 
610     if (!collect_locales_from_archive()) {
611 #ifndef WITH_INCOMPLETE_LOCALES
612         g_warning("Could not read list of available locales from libc, "
613                   "guessing possible locales from available translations, "
614                   "but list may be incomplete!");
615 #endif
616 
617         collect_locales_from_directory();
618     }
619     collect_locales_from_locale_file(ALIASES_FILE);
620 }
621 
622 static gboolean
is_fallback_language(const char * code)623 is_fallback_language(const char *code)
624 {
625     const char *fallback_language_names[] = { "C", "POSIX", NULL };
626     int i;
627 
628     for (i = 0; fallback_language_names[i] != NULL; i++) {
629         if (strcmp(code, fallback_language_names[i]) == 0) {
630             return TRUE;
631         }
632     }
633 
634     return FALSE;
635 }
636 
637 static const char *
get_language(const char * code)638 get_language(const char *code)
639 {
640     const char *name;
641     int         len;
642 
643     g_assert(code != NULL);
644 
645     if (is_fallback_language(code)) {
646         return "Unspecified";
647     }
648 
649     len = strlen(code);
650     if (len != 2 && len != 3) {
651         return NULL;
652     }
653 
654     name = (const char *) g_hash_table_lookup(gdm_languages_map, code);
655 
656     return name;
657 }
658 
659 static char *
get_first_item_in_semicolon_list(const char * list)660 get_first_item_in_semicolon_list(const char *list)
661 {
662     char **items;
663     char  *item;
664 
665     /* Some entries in iso codes have multiple values, separated
666      * by semicolons.  Not really sure which one to pick, so
667      * we just arbitrarily pick the first one.
668      */
669     items = g_strsplit(list, "; ", 2);
670 
671     item = g_strdup(items[0]);
672     g_strfreev(items);
673 
674     return item;
675 }
676 
677 static char *
get_translated_language(const char * code,const char * locale)678 get_translated_language(const char *code,
679                         const char *locale)
680 {
681     const char *language;
682     char *name;
683 
684     language = get_language(code);
685 
686     name = NULL;
687     if (language != NULL) {
688         const char  *translated_name;
689         char        *old_locale;
690 
691         if (locale != NULL) {
692             old_locale = g_strdup(setlocale(LC_MESSAGES, NULL));
693             setlocale(LC_MESSAGES, locale);
694         }
695 
696         if (is_fallback_language(code)) {
697             name = g_strdup(_("Unspecified"));
698         } else {
699             translated_name = dgettext("iso_639", language);
700             name = get_first_item_in_semicolon_list(translated_name);
701         }
702 
703         if (locale != NULL) {
704             setlocale(LC_MESSAGES, old_locale);
705             g_free(old_locale);
706         }
707     }
708 
709     return name;
710 }
711 
712 static const char *
get_territory(const char * code)713 get_territory(const char *code)
714 {
715     const char *name;
716     int         len;
717 
718     g_assert(code != NULL);
719 
720     len = strlen(code);
721     if (len != 2 && len != 3) {
722         return NULL;
723     }
724 
725     name = (const char *) g_hash_table_lookup(gdm_territories_map, code);
726 
727     return name;
728 }
729 
730 static char *
get_translated_territory(const char * code,const char * locale)731 get_translated_territory(const char *code,
732                          const char *locale)
733 {
734     const char *territory;
735     char       *name;
736 
737     territory = get_territory(code);
738 
739     name = NULL;
740     if (territory != NULL) {
741         const char *translated_territory;
742         char       *old_locale;
743 
744         if (locale != NULL) {
745             old_locale = g_strdup(setlocale(LC_MESSAGES, NULL));
746             setlocale(LC_MESSAGES, locale);
747         }
748 
749         translated_territory = dgettext("iso_3166", territory);
750         name = get_first_item_in_semicolon_list(translated_territory);
751 
752         if (locale != NULL) {
753             setlocale(LC_MESSAGES, old_locale);
754             g_free(old_locale);
755         }
756     }
757 
758     return name;
759 }
760 
761 static void
languages_parse_start_tag(GMarkupParseContext * ctx,const char * element_name,const char ** attr_names,const char ** attr_values,gpointer user_data,GError ** error)762 languages_parse_start_tag(GMarkupParseContext      *ctx,
763                           const char               *element_name,
764                           const char              **attr_names,
765                           const char              **attr_values,
766                           gpointer                  user_data,
767                           GError                  **error)
768 {
769     const char *ccode_longB;
770     const char *ccode_longT;
771     const char *ccode;
772     const char *lang_name;
773 
774     if (! g_str_equal(element_name, "iso_639_entry") || attr_names == NULL || attr_values == NULL) {
775         return;
776     }
777 
778     ccode = NULL;
779     ccode_longB = NULL;
780     ccode_longT = NULL;
781     lang_name = NULL;
782 
783     while (*attr_names && *attr_values) {
784         if (g_str_equal(*attr_names, "iso_639_1_code")) {
785             /* skip if empty */
786             if (**attr_values) {
787                 if (strlen(*attr_values) != 2) {
788                     return;
789                 }
790                 ccode = *attr_values;
791             }
792         } else if (g_str_equal(*attr_names, "iso_639_2B_code")) {
793             /* skip if empty */
794             if (**attr_values) {
795                 if (strlen(*attr_values) != 3) {
796                     return;
797                 }
798                 ccode_longB = *attr_values;
799             }
800         } else if (g_str_equal(*attr_names, "iso_639_2T_code")) {
801             /* skip if empty */
802             if (**attr_values) {
803                 if (strlen(*attr_values) != 3) {
804                     return;
805                 }
806                 ccode_longT = *attr_values;
807             }
808         } else if (g_str_equal(*attr_names, "name")) {
809             lang_name = *attr_values;
810         }
811 
812         ++attr_names;
813         ++attr_values;
814     }
815 
816     if (lang_name == NULL) {
817         return;
818     }
819 
820     if (ccode != NULL) {
821         g_hash_table_insert(gdm_languages_map,
822                             g_strdup(ccode),
823                             g_strdup(lang_name));
824     }
825     if (ccode_longB != NULL) {
826         g_hash_table_insert(gdm_languages_map,
827                             g_strdup(ccode_longB),
828                             g_strdup(lang_name));
829     }
830     if (ccode_longT != NULL) {
831         g_hash_table_insert(gdm_languages_map,
832                             g_strdup(ccode_longT),
833                             g_strdup(lang_name));
834     }
835 }
836 
837 static void
territories_parse_start_tag(GMarkupParseContext * ctx,const char * element_name,const char ** attr_names,const char ** attr_values,gpointer user_data,GError ** error)838 territories_parse_start_tag(GMarkupParseContext      *ctx,
839                             const char               *element_name,
840                             const char              **attr_names,
841                             const char              **attr_values,
842                             gpointer                  user_data,
843                             GError                  **error)
844 {
845     const char *acode_2;
846     const char *acode_3;
847     const char *ncode;
848     const char *territory_common_name;
849     const char *territory_name;
850 
851     if (! g_str_equal(element_name, "iso_3166_entry") || attr_names == NULL || attr_values == NULL) {
852         return;
853     }
854 
855     acode_2 = NULL;
856     acode_3 = NULL;
857     ncode = NULL;
858     territory_common_name = NULL;
859     territory_name = NULL;
860 
861     while (*attr_names && *attr_values) {
862         if (g_str_equal(*attr_names, "alpha_2_code")) {
863             /* skip if empty */
864             if (**attr_values) {
865                 if (strlen(*attr_values) != 2) {
866                     return;
867                 }
868                 acode_2 = *attr_values;
869             }
870         } else if (g_str_equal(*attr_names, "alpha_3_code")) {
871             /* skip if empty */
872             if (**attr_values) {
873                 if (strlen(*attr_values) != 3) {
874                     return;
875                 }
876                 acode_3 = *attr_values;
877             }
878         } else if (g_str_equal(*attr_names, "numeric_code")) {
879             /* skip if empty */
880             if (**attr_values) {
881                 if (strlen(*attr_values) != 3) {
882                     return;
883                 }
884                 ncode = *attr_values;
885             }
886         } else if (g_str_equal(*attr_names, "common_name")) {
887             /* skip if empty */
888             if (**attr_values) {
889                 territory_common_name = *attr_values;
890             }
891         } else if (g_str_equal(*attr_names, "name")) {
892             territory_name = *attr_values;
893         }
894 
895         ++attr_names;
896         ++attr_values;
897     }
898 
899     if (territory_common_name != NULL) {
900         territory_name = territory_common_name;
901     }
902 
903     if (territory_name == NULL) {
904         return;
905     }
906 
907     if (acode_2 != NULL) {
908         g_hash_table_insert(gdm_territories_map,
909                             g_strdup(acode_2),
910                             g_strdup(territory_name));
911     }
912     if (acode_3 != NULL) {
913         g_hash_table_insert(gdm_territories_map,
914                             g_strdup(acode_3),
915                             g_strdup(territory_name));
916     }
917     if (ncode != NULL) {
918         g_hash_table_insert(gdm_territories_map,
919                             g_strdup(ncode),
920                             g_strdup(territory_name));
921     }
922 }
923 
924 static void
languages_init(void)925 languages_init(void)
926 {
927     GError  *error;
928     gboolean res;
929     char    *buf;
930     gsize    buf_len;
931 
932     bindtextdomain("iso_639", ISO_CODES_LOCALESDIR);
933     bind_textdomain_codeset("iso_639", "UTF-8");
934 
935     gdm_languages_map = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
936 
937     error = NULL;
938     res = g_file_get_contents(ISO_CODES_DATADIR "/iso_639.xml",
939                               &buf,
940                               &buf_len,
941                               &error);
942     if (res) {
943         GMarkupParseContext *ctx;
944         GMarkupParser        parser = { languages_parse_start_tag, NULL, NULL, NULL, NULL };
945 
946         ctx = g_markup_parse_context_new(&parser, 0, NULL, NULL);
947 
948         error = NULL;
949         res = g_markup_parse_context_parse(ctx, buf, buf_len, &error);
950 
951         if (! res) {
952             g_warning("Failed to parse '%s': %s\n",
953                       ISO_CODES_DATADIR "/iso_639.xml",
954                       error->message);
955             g_error_free(error);
956         }
957 
958         g_markup_parse_context_free(ctx);
959         g_free(buf);
960     } else {
961         g_warning("Failed to load '%s': %s\n",
962                   ISO_CODES_DATADIR "/iso_639.xml",
963                   error->message);
964         g_error_free(error);
965     }
966 }
967 
968 static void
territories_init(void)969 territories_init(void)
970 {
971     GError  *error;
972     gboolean res;
973     char    *buf;
974     gsize    buf_len;
975 
976     bindtextdomain("iso_3166", ISO_CODES_LOCALESDIR);
977     bind_textdomain_codeset("iso_3166", "UTF-8");
978 
979     gdm_territories_map = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
980 
981     error = NULL;
982     res = g_file_get_contents(ISO_CODES_DATADIR "/iso_3166.xml",
983                               &buf,
984                               &buf_len,
985                               &error);
986     if (res) {
987         GMarkupParseContext *ctx;
988         GMarkupParser        parser = { territories_parse_start_tag, NULL, NULL, NULL, NULL };
989 
990         ctx = g_markup_parse_context_new(&parser, 0, NULL, NULL);
991 
992         error = NULL;
993         res = g_markup_parse_context_parse(ctx, buf, buf_len, &error);
994 
995         if (! res) {
996             g_warning("Failed to parse '%s': %s\n",
997                       ISO_CODES_DATADIR "/iso_3166.xml",
998                       error->message);
999             g_error_free(error);
1000         }
1001 
1002         g_markup_parse_context_free(ctx);
1003         g_free(buf);
1004     } else {
1005         g_warning("Failed to load '%s': %s\n",
1006                   ISO_CODES_DATADIR "/iso_3166.xml",
1007                   error->message);
1008         g_error_free(error);
1009     }
1010 }
1011 
1012 char *
gdm_get_language_from_name(const char * name,const char * locale)1013 gdm_get_language_from_name(const char *name,
1014                            const char *locale)
1015 {
1016     GString *full_language;
1017     char *language_code;
1018     char *territory_code;
1019     char *codeset_code;
1020     char *langinfo_codeset;
1021     char *translated_language;
1022     char *translated_territory;
1023     gboolean is_utf8 = TRUE;
1024 
1025     translated_territory = NULL;
1026     translated_language = NULL;
1027     langinfo_codeset = NULL;
1028 
1029     full_language = g_string_new(NULL);
1030 
1031     if (gdm_languages_map == NULL) {
1032         languages_init();
1033     }
1034 
1035     if (gdm_territories_map == NULL) {
1036         territories_init();
1037     }
1038 
1039     language_code = NULL;
1040     territory_code = NULL;
1041     codeset_code = NULL;
1042 
1043     gdm_parse_language_name(name,
1044                             &language_code,
1045                             &territory_code,
1046                             &codeset_code,
1047                             NULL);
1048 
1049     if (language_code == NULL) {
1050         goto out;
1051     }
1052 
1053     translated_language = get_translated_language(language_code, locale);
1054     if (translated_language == NULL) {
1055         goto out;
1056     }
1057 
1058     full_language = g_string_append(full_language, translated_language);
1059 
1060     if (territory_code != NULL) {
1061         translated_territory = get_translated_territory(territory_code, locale);
1062     }
1063     if (translated_territory != NULL) {
1064         g_string_append_printf(full_language,
1065                                " (%s)",
1066                                translated_territory);
1067     }
1068 
1069     language_name_get_codeset_details(name, &langinfo_codeset, &is_utf8);
1070 
1071     if (codeset_code == NULL && langinfo_codeset != NULL) {
1072         codeset_code = g_strdup(langinfo_codeset);
1073     }
1074 
1075 out:
1076     g_free(language_code);
1077     g_free(territory_code);
1078     g_free(codeset_code);
1079     g_free(langinfo_codeset);
1080     g_free(translated_language);
1081     g_free(translated_territory);
1082 
1083     if (full_language->len == 0) {
1084         g_string_free(full_language, TRUE);
1085         return NULL;
1086     }
1087 
1088     return g_string_free(full_language, FALSE);
1089 }
1090 
1091 char **
gdm_get_all_language_names(void)1092 gdm_get_all_language_names(void)
1093 {
1094     GHashTableIter iter;
1095     gpointer key, value;
1096     GPtrArray *array;
1097 
1098     if (gdm_available_locales_map == NULL) {
1099         collect_locales();
1100     }
1101 
1102     array = g_ptr_array_new();
1103     g_hash_table_iter_init(&iter, gdm_available_locales_map);
1104     while (g_hash_table_iter_next(&iter, &key, &value)) {
1105         GdmLocale *locale;
1106 
1107         locale = (GdmLocale *) value;
1108 
1109         g_ptr_array_add(array, g_strdup(locale->name));
1110     }
1111     g_ptr_array_add(array, NULL);
1112 
1113     return (char **) g_ptr_array_free(array, FALSE);
1114 }
1115