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