Greenbone Security Assistant  7.0.0
xslt_i18n.c
Go to the documentation of this file.
1 /* Greenbone Security Assistant
2  * $Id$
3  * Description: Translation libxslt extension of Greenbone Security Assistant.
4  *
5  * Authors:
6  * Timo Pollmeier <timo.pollmeier@greenbone.net>
7  *
8  * Copyright:
9  * Copyright (C) 2015 Greenbone Networks GmbH
10  *
11  * This program is free software; you can redistribute it and/or
12  * modify it under the terms of the GNU General Public License
13  * as published by the Free Software Foundation; either version 2
14  * of the License, or (at your option) any later version.
15  *
16  * This program is distributed in the hope that it will be useful,
17  * but WITHOUT ANY WARRANTY; without even the implied warranty of
18  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19  * GNU General Public License for more details.
20  *
21  * You should have received a copy of the GNU General Public License
22  * along with this program; if not, write to the Free Software
23  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
24  */
25 
26 #include "xslt_i18n.h"
27 #include "gsad_base.h"
28 #include <assert.h>
29 #include <dirent.h>
30 #include <errno.h>
31 #include <glib.h>
32 #include <libintl.h>
33 #include <libxml/xpath.h>
34 #include <libxml/xpathInternals.h>
35 #include <libxslt/xsltutils.h>
36 #include <math.h>
37 #include <string.h>
38 #include <locale.h>
39 #include <stdlib.h>
40 
41 #undef G_LOG_DOMAIN
42 
45 #define G_LOG_DOMAIN "gsad xslt"
46 
47 #ifndef GETTEXT_CONTEXT_GLUE
48 #define GETTEXT_CONTEXT_GLUE "\004"
49 #endif /* not GETTEXT_CONTEXT_GLUE */
50 
54 #define GSA_I18N_EXT_URI "http://openvas.org/i18n"
55 
59 #define GSA_XSL_TEXTDOMAIN "gsad_xsl"
60 
64 static GMutex locale_env_mutex;
65 
69 static int ext_gettext_enabled = 0;
70 
74 static GList *installed_languages = NULL;
75 
79 static GHashTable *language_names = NULL;
80 static GHashTable *native_language_names = NULL;
81 
88 static void
89 xslt_ext_gettext (xmlXPathParserContextPtr ctxt,
90  int nargs)
91 {
92  xmlXPathObjectPtr lang_obj, msgid_obj, context_obj;
93  xmlChar* result_str;
94  xmlXPathObjectPtr result_obj;
95 
96  /*
97  * Function arguments:
98  * - language
99  * - msgid
100  * - context (optional)
101  */
102 
103  if (nargs < 2 || nargs > 3)
104  {
105  xsltGenericError (xsltGenericErrorContext,
106  "gettext : Expected 2 or 3 arguments, got %d\n",
107  nargs);
108  return;
109  }
110 
111  if (nargs == 3)
112  {
113  context_obj = valuePop (ctxt);
114  if (context_obj->type != XPATH_STRING)
115  {
116  valuePush (ctxt, context_obj);
117  xmlXPathStringFunction (ctxt, 1);
118  context_obj = valuePop (ctxt);
119  }
120  }
121  else
122  context_obj = NULL;
123 
124  msgid_obj = valuePop (ctxt);
125  if (msgid_obj->type != XPATH_STRING)
126  {
127  valuePush (ctxt, msgid_obj);
128  xmlXPathStringFunction (ctxt, 1);
129  msgid_obj = valuePop (ctxt);
130  }
131 
132  lang_obj = valuePop (ctxt);
133  if (lang_obj->type != XPATH_STRING)
134  {
135  valuePush (ctxt, lang_obj);
136  xmlXPathStringFunction (ctxt, 1);
137  lang_obj = valuePop (ctxt);
138  }
139 
140  if (ext_gettext_enabled == 0)
141  {
142  valuePush (ctxt, msgid_obj);
143 
144  xmlXPathFreeObject (lang_obj);
145  if (context_obj)
146  xmlXPathFreeObject (context_obj);
147 
148  return;
149  }
150 
151  if (msgid_obj->stringval && strcmp ((char*) msgid_obj->stringval, ""))
152  {
153  gchar *old_locale;
154  char *old_LANGUAGE;
155  gchar *msgid = NULL;
156  char *gettext_result = NULL;
157 
158  g_mutex_lock (&locale_env_mutex);
159 
160  old_locale = g_strdup (setlocale (LC_ALL, NULL));
161  old_LANGUAGE = getenv ("LANGUAGE");
162  setenv ("LANGUAGE", (char*)lang_obj->stringval, 1);
163  setlocale (LC_ALL, "");
164 
165  if (context_obj)
166  msgid = g_strdup_printf ("%s%s%s",
167  (gchar*) context_obj->stringval,
169  (gchar*) msgid_obj->stringval);
170  else
171  msgid = g_strdup ((gchar*) msgid_obj->stringval);
172 
173  textdomain (GSA_XSL_TEXTDOMAIN);
174  gettext_result = gettext (msgid);
175  result_str = (xmlChar*) g_strdup ((gettext_result != msgid)
176  ? gettext_result
177  : "### N/A ###");
178  g_free (msgid);
179 
180  if (old_LANGUAGE)
181  setenv ("LANGUAGE", old_LANGUAGE, 1);
182  else
183  unsetenv ("LANGUAGE");
184  setlocale (LC_ALL, old_locale);
185  g_free (old_locale);
186 
187  g_mutex_unlock (&locale_env_mutex);
188  }
189  else
190  result_str = (xmlChar*) g_strdup ("");
191 
192  result_obj = xmlXPathNewString (result_str);
193 
194  xmlXPathFreeObject (lang_obj);
195  xmlXPathFreeObject (msgid_obj);
196  if (context_obj)
197  xmlXPathFreeObject (context_obj);
198  g_free (result_str);
199 
200  valuePush (ctxt, result_obj);
201 }
202 
209 static void
210 xslt_ext_ngettext (xmlXPathParserContextPtr ctxt,
211  int nargs)
212 {
213  xmlXPathObjectPtr lang_obj, msgid_obj, msgid_pl_obj, count_obj, context_obj;
214  xmlChar* result_str;
215  xmlXPathObjectPtr result_obj;
216 
217  /*
218  * Function arguments:
219  * - language
220  * - msgid
221  * - msgid_plural
222  * - count
223  * - context (optional)
224  */
225 
226  if (nargs < 4 || nargs > 5)
227  {
228  xsltGenericError (xsltGenericErrorContext,
229  "ngettext : Expected 4 or 5 arguments, got %d\n",
230  nargs);
231  return;
232  }
233 
234  if (nargs == 5)
235  {
236  context_obj = valuePop (ctxt);
237  if (context_obj->type != XPATH_STRING)
238  {
239  valuePush (ctxt, context_obj);
240  xmlXPathStringFunction (ctxt, 1);
241  context_obj = valuePop (ctxt);
242  }
243  }
244  else
245  context_obj = NULL;
246 
247  count_obj = valuePop (ctxt);
248  if (count_obj->type != XPATH_NUMBER)
249  {
250  valuePush (ctxt, count_obj);
251  xmlXPathNumberFunction (ctxt, 1);
252  count_obj = valuePop (ctxt);
253 
254  if (count_obj->type != XPATH_NUMBER || isnan (count_obj->floatval))
255  xsltGenericError (xsltGenericErrorContext,
256  "ngettext : 4th argument cannot be converted"
257  " to a valid number\n");
258  }
259 
260  msgid_pl_obj = valuePop (ctxt);
261  if (msgid_pl_obj->type != XPATH_STRING)
262  {
263  valuePush (ctxt, msgid_pl_obj);
264  xmlXPathStringFunction (ctxt, 1);
265  msgid_pl_obj = valuePop (ctxt);
266  }
267 
268  msgid_obj = valuePop (ctxt);
269  if (msgid_obj->type != XPATH_STRING)
270  {
271  valuePush (ctxt, msgid_obj);
272  xmlXPathStringFunction (ctxt, 1);
273  msgid_obj = valuePop (ctxt);
274  }
275 
276  lang_obj = valuePop (ctxt);
277  if (lang_obj->type != XPATH_STRING)
278  {
279  valuePush (ctxt, lang_obj);
280  xmlXPathStringFunction (ctxt, 1);
281  lang_obj = valuePop (ctxt);
282  }
283 
284  if (ext_gettext_enabled == 0)
285  {
286  unsigned long count;
287 
288  count = (unsigned long) count_obj->floatval;
289 
290  if (count == 1)
291  {
292  xmlXPathFreeObject (msgid_pl_obj);
293  valuePush (ctxt, msgid_obj);
294  }
295  else
296  {
297  xmlXPathFreeObject (msgid_obj);
298  valuePush (ctxt, msgid_pl_obj);
299  }
300 
301  xmlXPathFreeObject (lang_obj);
302  xmlXPathFreeObject (count_obj);
303  if (context_obj)
304  xmlXPathFreeObject (context_obj);
305 
306  return;
307  }
308 
309  if (msgid_obj->stringval && strcmp ((char*) msgid_obj->stringval, ""))
310  {
311  gchar *old_locale;
312  char *old_LANGUAGE;
313  gchar *msgid = NULL;
314  gchar *msgid_pl = NULL;
315  unsigned long count;
316  char *gettext_result = NULL;
317 
318  g_mutex_lock (&locale_env_mutex);
319 
320  old_locale = g_strdup (setlocale (LC_ALL, NULL));
321  old_LANGUAGE = getenv ("LANGUAGE");
322  setenv ("LANGUAGE", (char*)lang_obj->stringval, 1);
323  setlocale (LC_ALL, "");
324 
325  if (context_obj)
326  msgid = g_strdup_printf ("%s%s%s",
327  (gchar*) context_obj->stringval,
329  (gchar*) msgid_obj->stringval);
330  else
331  msgid = g_strdup ((gchar*) msgid_obj->stringval);
332 
333  if (context_obj && context_obj->stringval)
334  msgid_pl = g_strdup_printf ("%s%s%s",
335  (gchar*) context_obj->stringval,
337  (gchar*) msgid_pl_obj->stringval);
338  else
339  msgid_pl = g_strdup ((gchar*) msgid_pl_obj->stringval);
340 
341  count = (unsigned long) count_obj->floatval;
342 
343  textdomain (GSA_XSL_TEXTDOMAIN);
344  gettext_result = ngettext (msgid, msgid_pl, count);
345  result_str = (xmlChar*) g_strdup ((gettext_result != msgid
346  && gettext_result != msgid_pl)
347  ? gettext_result
348  : "### N/A ###");
349  g_free (msgid);
350 
351  if (old_LANGUAGE)
352  setenv ("LANGUAGE", old_LANGUAGE, 1);
353  else
354  unsetenv ("LANGUAGE");
355  setlocale (LC_ALL, old_locale);
356  g_free (old_locale);
357 
358  g_mutex_unlock (&locale_env_mutex);
359  }
360  else
361  result_str = (xmlChar*) g_strdup ("");
362 
363  result_obj = xmlXPathNewString (result_str);
364 
365  xmlXPathFreeObject (lang_obj);
366  xmlXPathFreeObject (msgid_obj);
367  xmlXPathFreeObject (msgid_pl_obj);
368  xmlXPathFreeObject (count_obj);
369  if (context_obj)
370  xmlXPathFreeObject (context_obj);
371  g_free (result_str);
372 
373  valuePush (ctxt, result_obj);
374 }
375 
382 static void
383 xslt_ext_strformat (xmlXPathParserContextPtr ctxt,
384  int nargs)
385 {
386  GArray *format_args;
387  int i, pos, format_string_len, in_string_number;
388  xmlXPathObjectPtr format_string_obj, result_obj;
389  gchar *format_string;
390  GString *result_str, *number_str;
391 
392  /*
393  * Function arguments:
394  * - format_string
395  * - format_args (variadic)
396  */
397 
398  format_args = g_array_sized_new (TRUE, TRUE, sizeof (gchar*), nargs-1);
399 
400  for (i = 0; i < nargs-1; i++)
401  {
402  xmlXPathObjectPtr format_arg_obj;
403  gchar *new_string;
404 
405  format_arg_obj = valuePop (ctxt);
406  if (format_arg_obj->type != XPATH_STRING)
407  {
408  valuePush (ctxt, format_arg_obj);
409  xmlXPathStringFunction (ctxt, 1);
410  format_arg_obj = valuePop (ctxt);
411  }
412 
413  new_string = g_strdup ((gchar*) format_arg_obj->stringval);
414  g_array_prepend_val (format_args, new_string);
415  xmlXPathFreeObject (format_arg_obj);
416  format_arg_obj = NULL;
417  }
418 
419  format_string_obj = valuePop (ctxt);
420  if (format_string_obj->type != XPATH_STRING)
421  {
422  valuePush (ctxt, format_string_obj);
423  xmlXPathStringFunction (ctxt, 1);
424  format_string_obj = valuePop (ctxt);
425  }
426  format_string = g_strdup ((gchar*) format_string_obj->stringval);
427  xmlXPathFreeObject (format_string_obj);
428 
429  result_str = g_string_sized_new (strlen (format_string));
430  number_str = g_string_sized_new (3);
431  format_string_len = strlen (format_string);
432  in_string_number = 0;
433 
434  for (pos = 0; pos <= format_string_len; pos++)
435  {
436  if (in_string_number)
437  {
438  if (format_string [pos] >= '0' && format_string [pos] <= '9')
439  {
440  g_string_append_c (number_str, format_string [pos]);
441  }
442  else
443  {
444  int arg_number = atoi (number_str->str);
445 
446  if (arg_number > 0 && arg_number <= format_args->len)
447  {
448  g_string_append (result_str,
449  g_array_index (format_args, gchar*,
450  arg_number - 1));
451  }
452 
453  g_string_append_c (result_str, format_string [pos]);
454  g_string_erase (number_str, 0, number_str->len);
455  in_string_number = 0;
456  }
457  }
458  else
459  {
460  if (format_string [pos] == '%')
461  in_string_number = 1;
462  else
463  g_string_append_c (result_str, format_string [pos]);
464  }
465  }
466 
467  result_obj = xmlXPathNewString ((xmlChar*) result_str->str);
468 
469  {
470  guint index = format_args->len;
471  while (index--)
472  g_free (g_array_index (format_args, gchar*, index));
473  g_array_free (format_args, TRUE);
474  }
475  g_free (format_string);
476  g_string_free (number_str, TRUE);
477  g_string_free (result_str, TRUE);
478  valuePush (ctxt, result_obj);
479 }
480 
487 static void*
488 init_i18n_module (xsltTransformContextPtr ctxt,
489  const xmlChar *URI)
490 {
491  xsltRegisterExtFunction (ctxt,
492  (xmlChar*) "gettext",
493  URI,
494  xslt_ext_gettext);
495 
496  xsltRegisterExtFunction (ctxt,
497  (xmlChar*) "ngettext",
498  URI,
499  xslt_ext_ngettext);
500 
501  xsltRegisterExtFunction (ctxt,
502  (xmlChar*) "strformat",
503  URI,
504  xslt_ext_strformat);
505 
506  return NULL;
507 };
508 
516 static void
517 shutdown_i18n_module (xsltTransformContextPtr ctxt,
518  const xmlChar *URI,
519  void *data)
520 {
521  xsltUnregisterExtModuleFunction ((xmlChar*) "gettext",
522  URI);
523  xsltUnregisterExtModuleFunction ((xmlChar*) "ngettext",
524  URI);
525  xsltUnregisterExtModuleFunction ((xmlChar*) "strformat",
526  URI);
527 };
528 
532 void
534 {
535  g_debug ("Registering i18n XSLT module");
536 
537  xsltRegisterExtModule((xmlChar*) GSA_I18N_EXT_URI,
538  init_i18n_module,
539  shutdown_i18n_module);
540 
541  if (bindtextdomain (GSA_XSL_TEXTDOMAIN,
542  get_chroot_state () ? GSA_CHROOT_LOCALE_DIR
543  : GSA_LOCALE_DIR)
544  == NULL)
545  {
546  g_critical ("%s: Failed to bind text domain for gettext", __FUNCTION__);
547  abort ();
548  }
549 
550 }
551 
557 int
559 {
560  return ext_gettext_enabled;
561 }
562 
568 void
570 {
571  ext_gettext_enabled = (enabled != 0);
572 }
573 
579 int
581 {
582  FILE *lang_names_file;
583  const char *locale_dir_name;
584  DIR *locale_dir;
585  struct dirent *entry;
586 
587  if (installed_languages != NULL)
588  {
589  g_warning ("%s: Language lists already initialized.", __FUNCTION__);
590  return -1;
591  }
592 
593  /* Init data structures */
594  language_names = g_hash_table_new_full (g_str_hash, g_str_equal,
595  g_free, g_free);
596 
597  native_language_names = g_hash_table_new_full (g_str_hash, g_str_equal,
598  g_free, g_free);
599 
600  // installed_languages starts initialized as NULL
601 
602  /* Add presets "Browser Language" and "English" */
603  installed_languages = g_list_append (installed_languages,
604  g_strdup ("Browser Language"));
605  installed_languages = g_list_append (installed_languages,
606  g_strdup ("en"));
607  g_hash_table_insert (language_names,
608  g_strdup ("Browser Language"),
609  g_strdup ("Browser Language"));
610  g_hash_table_insert (native_language_names,
611  g_strdup ("Browser Language"),
612  g_strdup ("Browser Language"));
613  g_hash_table_insert (language_names,
614  g_strdup ("en"), g_strdup ("English"));
615  g_hash_table_insert (native_language_names,
616  g_strdup ("en"), g_strdup ("English"));
617 
618  /* Get language names */
619  lang_names_file = fopen (GSA_DATA_DIR "/language_names.tsv", "r");
620  if (lang_names_file)
621  {
622  size_t len;
623  char *line = NULL;
624  while (getline (&line, &len, lang_names_file) != -1)
625  {
626  g_strstrip (line);
627  if (line [0] != '\0' && line [0] != '#')
628  {
629  gchar **columns;
630  gchar *code, *name, *native_name;
631  columns = g_strsplit (line, "\t", 3);
632  code = columns [0];
633  name = code ? columns [1] : NULL;
634  native_name = name ? columns [2] : NULL;
635  if (code && name)
636  g_hash_table_insert (language_names,
637  g_strdup (code),
638  g_strdup (name));
639  if (code && native_name)
640  g_hash_table_insert (native_language_names,
641  g_strdup (code),
642  g_strdup (native_name));
643  g_strfreev (columns);
644  }
645  g_free (line);
646  line = NULL;
647  }
648  fclose (lang_names_file);
649  }
650  else
651  {
652  g_warning ("%s: Failed to open language names file: %s",
653  __FUNCTION__, strerror (errno));
654  }
655 
656  /* Get installed translations */
657  locale_dir_name = get_chroot_state () ? GSA_CHROOT_LOCALE_DIR
658  : GSA_LOCALE_DIR;
659  locale_dir = opendir (locale_dir_name);
660 
661  if (locale_dir == NULL)
662  {
663  g_warning ("%s: Failed to open locale directory \"%s\": %s",
664  __FUNCTION__, GSA_LOCALE_DIR, strerror (errno));
665  return -1;
666  }
667 
668  while ((entry = readdir (locale_dir)) != 0)
669  {
670  if (entry->d_name[0] != '.'
671  && strlen (entry->d_name) >= 2
672  && entry->d_type == DT_DIR
673  && strcmp (entry->d_name, "en")
674  && strcmp (entry->d_name, "Browser Language"))
675  {
676  FILE *mo_file;
677  gchar *lang_mo_path;
678  lang_mo_path = g_build_filename (locale_dir_name,
679  entry->d_name,
680  "LC_MESSAGES",
681  GSA_XSL_TEXTDOMAIN ".mo",
682  NULL);
683 
684  mo_file = fopen (lang_mo_path, "r");
685  if (mo_file)
686  {
687  fclose (mo_file);
688  installed_languages
689  = g_list_insert_sorted (installed_languages,
690  g_strdup (entry->d_name),
691  (GCompareFunc) strcmp);
692  }
693  else
694  {
695  if (errno != ENOENT)
696  g_warning ("%s: Failed to open %s: %s",
697  __FUNCTION__, lang_mo_path, strerror (errno));
698  }
699  g_free (lang_mo_path);
700  }
701  }
702  closedir (locale_dir);
703 
704  GString *test = g_string_new ("");
705  buffer_languages_xml (test);
706  g_debug ("%s: Initialized language lists", __FUNCTION__);
707  g_string_free (test, TRUE);
708 
709  return 0;
710 }
711 
717 void
718 buffer_languages_xml (GString *buffer)
719 {
720  GList *langs_list;
721  assert (buffer);
722 
723  langs_list = g_list_first (installed_languages);
724 
725  g_string_append (buffer, "<gsa_languages>");
726  while (langs_list)
727  {
728  gchar *lang_code, *lang_name, *native_name, *language_escaped;
729 
730  lang_code = (gchar*) langs_list->data;
731 
732  lang_name = g_hash_table_lookup (language_names, lang_code);
733  if (lang_name == NULL)
734  lang_name = lang_code;
735 
736  native_name = g_hash_table_lookup (native_language_names, lang_code);
737  if (native_name == NULL)
738  native_name = lang_name;
739 
740  language_escaped
741  = g_markup_printf_escaped ("<language>"
742  "<code>%s</code>"
743  "<name>%s</name>"
744  "<native_name>%s</native_name>"
745  "</language>",
746  lang_code,
747  lang_name,
748  native_name);
749  g_string_append (buffer, language_escaped);
750  g_free (language_escaped);
751  langs_list = g_list_nth (langs_list, 1);
752  }
753  g_string_append (buffer, "</gsa_languages>");
754 }
755 
768 gchar *
769 accept_language_to_env_fmt (const char* accept_language)
770 {
771  if (accept_language == NULL)
772  return g_strdup (DEFAULT_GSAD_LANGUAGE);
773 
774  gchar *language;
775  // TODO: Convert to a colon-separated list of codes instead of
776  // just extracting the first one
777  gchar **prefs, *pref;
778  prefs = g_strsplit_set (accept_language, ",;", -1);
779 
780  pref = prefs [0];
781  if (pref)
782  {
783  char *pos;
784  g_strstrip (pref);
785  pos = pref;
786  while (pos[0] != '\0')
787  {
788  if (pos[0] == '-')
789  pos[0] = '_';
790  pos++;
791  };
792  }
793  language = g_strdup (pref ? pref : DEFAULT_GSAD_LANGUAGE);
794  g_strfreev (prefs);
795 
796  return language;
797 }
#define GSA_I18N_EXT_URI
Namespace URI for the i18n XSLT extension.
Definition: xslt_i18n.c:54
int get_ext_gettext_enabled()
Get whether gettext functions for extensions are enabled.
Definition: xslt_i18n.c:558
void set_ext_gettext_enabled(int enabled)
Enable or disable gettext functions for extensions.
Definition: xslt_i18n.c:569
#define DEFAULT_GSAD_LANGUAGE
Default language code, used when Accept-Language header is missing.
Definition: xslt_i18n.h:35
gchar * accept_language_to_env_fmt(const char *accept_language)
Convert an Accept-Language string to the LANGUAGE env variable form.
Definition: xslt_i18n.c:769
void register_i18n_ext_module()
Register the i18n XSLT extension module.
Definition: xslt_i18n.c:533
Headers/structs used generally in GSA.
#define GSA_XSL_TEXTDOMAIN
Definition: xslt_i18n.c:59
int init_language_lists()
Initialize the list of available languages.
Definition: xslt_i18n.c:580
int get_chroot_state()
Gets the chroot state.
Definition: gsad_base.c:114
#define GETTEXT_CONTEXT_GLUE
Definition: xslt_i18n.c:48
void buffer_languages_xml(GString *buffer)
Write the list of installed languages to a buffer as XML.
Definition: xslt_i18n.c:718