/* Reading Desktop Entry files. Copyright (C) 1995-1998, 2000-2003, 2005-2006, 2008-2009, 2014-2016 Free Software Foundation, Inc. This file was written by Daiki Ueno . This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifdef HAVE_CONFIG_H # include #endif /* Specification. */ #include "read-desktop.h" #include "xalloc.h" #include #include #include #include #include #include #include "error.h" #include "error-progname.h" #include "xalloc.h" #include "xvasprintf.h" #include "c-ctype.h" #include "po-lex.h" #include "po-xerror.h" #include "gettext.h" #define _(str) gettext (str) #define SIZEOF(a) (sizeof(a) / sizeof(a[0])) /* The syntax of a Desktop Entry file is defined at http://standards.freedesktop.org/desktop-entry-spec/latest/index.html. */ desktop_reader_ty * desktop_reader_alloc (desktop_reader_class_ty *method_table) { desktop_reader_ty *reader; reader = (desktop_reader_ty *) xmalloc (method_table->size); reader->methods = method_table; if (method_table->constructor) method_table->constructor (reader); return reader; } void desktop_reader_free (desktop_reader_ty *reader) { if (reader->methods->destructor) reader->methods->destructor (reader); free (reader); } void desktop_reader_handle_group (desktop_reader_ty *reader, const char *group) { if (reader->methods->handle_group) reader->methods->handle_group (reader, group); } void desktop_reader_handle_pair (desktop_reader_ty *reader, lex_pos_ty *key_pos, const char *key, const char *locale, const char *value) { if (reader->methods->handle_pair) reader->methods->handle_pair (reader, key_pos, key, locale, value); } void desktop_reader_handle_comment (desktop_reader_ty *reader, const char *s) { if (reader->methods->handle_comment) reader->methods->handle_comment (reader, s); } void desktop_reader_handle_blank (desktop_reader_ty *reader, const char *s) { if (reader->methods->handle_blank) reader->methods->handle_blank (reader, s); } /* Real filename, used in error messages about the input file. */ static const char *real_file_name; /* File name and line number. */ extern lex_pos_ty gram_pos; /* The input file stream. */ static FILE *fp; static int phase1_getc () { int c; c = getc (fp); if (c == EOF) { if (ferror (fp)) { const char *errno_description = strerror (errno); po_xerror (PO_SEVERITY_FATAL_ERROR, NULL, NULL, 0, 0, false, xasprintf ("%s: %s", xasprintf (_("error while reading \"%s\""), real_file_name), errno_description)); } return EOF; } return c; } static inline void phase1_ungetc (int c) { if (c != EOF) ungetc (c, fp); } static unsigned char phase2_pushback[2]; static int phase2_pushback_length; static int phase2_getc () { int c; if (phase2_pushback_length) c = phase2_pushback[--phase2_pushback_length]; else { c = phase1_getc (); if (c == '\r') { int c2 = phase1_getc (); if (c2 == '\n') c = c2; else phase1_ungetc (c2); } } if (c == '\n') gram_pos.line_number++; return c; } static void phase2_ungetc (int c) { if (c == '\n') --gram_pos.line_number; if (c != EOF) phase2_pushback[phase2_pushback_length++] = c; } enum token_type_ty { token_type_eof, token_type_group, token_type_pair, /* Unlike other scanners, preserve comments and blank lines for merging translations back into a desktop file, with msgfmt. */ token_type_comment, token_type_blank, token_type_other }; typedef enum token_type_ty token_type_ty; typedef struct token_ty token_ty; struct token_ty { token_type_ty type; char *string; const char *value; const char *locale; }; /* Free the memory pointed to by a 'struct token_ty'. */ static inline void free_token (token_ty *tp) { if (tp->type == token_type_group || tp->type == token_type_pair || tp->type == token_type_comment || tp->type == token_type_blank) free (tp->string); } static void desktop_lex (token_ty *tp) { static char *buffer; static size_t bufmax; size_t bufpos; #undef APPEND #define APPEND(c) \ do \ { \ if (bufpos >= bufmax) \ { \ bufmax += 100; \ buffer = xrealloc (buffer, bufmax); \ } \ buffer[bufpos++] = c; \ } \ while (0) bufpos = 0; for (;;) { int c; c = phase2_getc (); switch (c) { case EOF: tp->type = token_type_eof; return; case '[': { bool non_blank = false; for (;;) { c = phase2_getc (); switch (c) { default: /* Group names may contain all ASCII characters except for '[' and ']' and control characters. */ if (!(c_isascii (c) && c != '[') && !c_iscntrl (c)) break; APPEND (c); continue; case '\n': po_xerror (PO_SEVERITY_WARNING, NULL, real_file_name, gram_pos.line_number, 0, false, _("unterminated group name")); break; case EOF: case ']': break; } break; } /* Skip until newline. */ while (c != '\n' && c != EOF) { c = phase2_getc (); if (c == EOF) break; if (!c_isspace (c)) non_blank = true; } if (non_blank) po_xerror (PO_SEVERITY_WARNING, NULL, real_file_name, gram_pos.line_number, 0, false, _("invalid non-blank character")); APPEND (0); tp->type = token_type_group; tp->string = xstrdup (buffer); return; } case '#': { /* Read until newline. */ for (;;) { c = phase2_getc (); switch (c) { default: APPEND (c); continue; case EOF: case '\n': break; } break; } APPEND (0); tp->type = token_type_comment; tp->string = xstrdup (buffer); return; } case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': case 'G': case 'H': case 'I': case 'J': case 'K': case 'L': case 'M': case 'N': case 'O': case 'P': case 'Q': case 'R': case 'S': case 'T': case 'U': case 'V': case 'W': case 'X': case 'Y': case 'Z': case '-': case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': case 'g': case 'h': case 'i': case 'j': case 'k': case 'l': case 'm': case 'n': case 'o': case 'p': case 'q': case 'r': case 's': case 't': case 'u': case 'v': case 'w': case 'x': case 'y': case 'z': case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': { size_t locale_start; bool found_locale = false; size_t value_start; for (;;) { APPEND (c); c = phase2_getc (); switch (c) { case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': case 'G': case 'H': case 'I': case 'J': case 'K': case 'L': case 'M': case 'N': case 'O': case 'P': case 'Q': case 'R': case 'S': case 'T': case 'U': case 'V': case 'W': case 'X': case 'Y': case 'Z': case '-': case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': case 'g': case 'h': case 'i': case 'j': case 'k': case 'l': case 'm': case 'n': case 'o': case 'p': case 'q': case 'r': case 's': case 't': case 'u': case 'v': case 'w': case 'x': case 'y': case 'z': case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': continue; case '[': /* Finish the key part and start the locale part. */ APPEND (0); found_locale = true; locale_start = bufpos; for (;;) { int c2 = phase2_getc (); switch (c2) { default: APPEND (c2); continue; case EOF: case ']': break; } break; } break; default: phase2_ungetc (c); break; } break; } APPEND (0); /* Skip any whitespace before '='. */ for (;;) { c = phase2_getc (); switch (c) { default: if (c_isspace (c)) continue; phase2_ungetc (c); break; case EOF: case '\n': break; } break; } c = phase2_getc (); if (c != '=') { po_xerror (PO_SEVERITY_WARNING, NULL, real_file_name, gram_pos.line_number, 0, false, xasprintf (_("missing '=' after \"%s\""), buffer)); for (;;) { c = phase2_getc (); if (c == EOF || c == '\n') break; } tp->type = token_type_other; return; } /* Skip any whitespace after '='. */ for (;;) { c = phase2_getc (); switch (c) { default: if (c_isspace (c)) continue; phase2_ungetc (c); break; case EOF: case '\n': break; } break; } value_start = bufpos; for (;;) { c = phase2_getc (); if (c == EOF || c == '\n') break; APPEND (c); } APPEND (0); tp->type = token_type_pair; tp->string = xmemdup (buffer, bufpos); tp->locale = found_locale ? &buffer[locale_start] : NULL; tp->value = &buffer[value_start]; return; } default: { bool non_blank = false; for (;;) { if (c == '\n' || c == EOF) break; if (!c_isspace (c)) non_blank = true; else APPEND (c); c = phase2_getc (); } if (non_blank) { po_xerror (PO_SEVERITY_WARNING, NULL, real_file_name, gram_pos.line_number, 0, false, _("invalid non-blank line")); tp->type = token_type_other; return; } APPEND (0); tp->type = token_type_blank; tp->string = xstrdup (buffer); return; } } } #undef APPEND } void desktop_parse (desktop_reader_ty *reader, FILE *file, const char *real_filename, const char *logical_filename) { fp = file; real_file_name = real_filename; gram_pos.file_name = xstrdup (logical_filename); gram_pos.line_number = 1; for (;;) { struct token_ty token; desktop_lex (&token); switch (token.type) { case token_type_eof: goto out; case token_type_group: desktop_reader_handle_group (reader, token.string); break; case token_type_comment: desktop_reader_handle_comment (reader, token.string); break; case token_type_pair: desktop_reader_handle_pair (reader, &gram_pos, token.string, token.locale, token.value); break; case token_type_blank: desktop_reader_handle_blank (reader, token.string); break; case token_type_other: break; } free_token (&token); } out: fp = NULL; real_file_name = NULL; gram_pos.line_number = 0; } char * desktop_escape_string (const char *s, bool is_list) { char *buffer, *p; p = buffer = XNMALLOC (strlen (s) * 2 + 1, char); /* The first character must not be a whitespace. */ if (*s == ' ') { p = stpcpy (p, "\\s"); s++; } else if (*s == '\t') { p = stpcpy (p, "\\t"); s++; } for (;; s++) { if (*s == '\0') { *p = '\0'; break; } switch (*s) { case '\n': p = stpcpy (p, "\\n"); break; case '\r': p = stpcpy (p, "\\r"); break; case '\\': if (is_list && *(s + 1) == ';') { p = stpcpy (p, "\\;"); s++; } else p = stpcpy (p, "\\\\"); break; default: *p++ = *s; break; } } return buffer; } char * desktop_unescape_string (const char *s, bool is_list) { char *buffer, *p; p = buffer = XNMALLOC (strlen (s) + 1, char); for (;; s++) { if (*s == '\0') { *p = '\0'; break; } if (*s == '\\') { s++; if (*s == '\0') { *p = '\0'; break; } switch (*s) { case 's': *p++ = ' '; break; case 'n': *p++ = '\n'; break; case 't': *p++ = '\t'; break; case 'r': *p++ = '\r'; break; case ';': p = stpcpy (p, "\\;"); break; default: *p++ = *s; break; } } else *p++ = *s; } return buffer; } void desktop_add_keyword (hash_table *keywords, const char *name, bool is_list) { hash_insert_entry (keywords, name, strlen (name), (void *) is_list); } void desktop_add_default_keywords (hash_table *keywords) { /* When adding new keywords here, also update the documentation in xgettext.texi! */ desktop_add_keyword (keywords, "Name", false); desktop_add_keyword (keywords, "GenericName", false); desktop_add_keyword (keywords, "Comment", false); desktop_add_keyword (keywords, "Icon", false); desktop_add_keyword (keywords, "Keywords", true); }