poe

Уређивач .po фајлова
git clone https://git.sr.ht/~strahinja/poe
Дневник | Датотеке | Референце | ПРОЧИТАЈМЕ | ЛИЦЕНЦА

чување d1019afbfcd2a3efc2dcf18a03d1d285d28f0d72
родитељ 49f04f3225ed8a22d58ace022f10af1c2e83cb28
Аутор: Страхиња Радић <sr@strahinja.org>
Датум:   Thu, 12 Feb 2026 18:56:09 +0100

po.c(load_file): Improve binary file and syntax check; (parse_po_line): Improve parsing

Diffstat:
Mpo.c | 270+++++++++++++++++++++++++++++++++++++++++++++++++------------------------------
Mpo.h | 41++++++++++++++++++++---------------------
Mpoe.c | 13+++++++++++--
измењених датотека: 3, додавања: 200(+), брисања: 124(-)

diff --git a/po.c b/po.c @@ -421,18 +421,19 @@ add_ref_comment(struct PoEntry* entry, const char* comment) int load_file(struct DrawState* dstate, long* lineno, long* col) { - struct PoEntry* newchunk = NULL; - struct PoEntry* entry = NULL; - struct PoEntry* current = NULL; - FILE* input = NULL; - char* input_line = NULL; + struct PoEntry* newchunk = NULL; + struct PoEntry* entry = NULL; + struct PoEntry* current = NULL; + FILE* input = NULL; + char* input_line = NULL; ParseState pstate = PS_NONE; int has_plural_msgid = 0; - ssize_t input_size = limits[L_LINE_MAX]; + ssize_t input_size = limits[L_LINE_MAX]; ssize_t non_obsolete_sofar = 0; /* lines to tolerate the lack of msgid */ int msgid_counter = PO_DETECTION_LINES; int msgstr_index = -1; + int possible_nulbyte; assert(dstate != NULL); input_line = malloc(input_size); @@ -455,27 +456,44 @@ load_file(struct DrawState* dstate, long* lineno, long* col) dstate->action = ACT_READING; redraw_status(dstate); + possible_nulbyte = 0; + *lineno = 1; while (!feof(input)) { - char* eol = NULL; - int result; + char* eol = NULL; + char* nulbyte = NULL; if (!fgets(input_line, input_size, input)) continue; + if (possible_nulbyte) + { + /* Definitely not EOF, NUL byte confirmed */ + dstate->error = ERR_NOTPO_FILE; + fclose(input); + free(input_line); + return 1; + } eol = strchr(input_line, '\n'); if (eol) *eol = 0; + nulbyte = strchr(input_line, 0); - result = parse_po_line(input_line, dstate, &entry, &pstate, col, + /* We don't know if the NUL byte not being equal to + * (possibly NULL) eol is the result of EOF or not + */ + if (nulbyte && (nulbyte != eol)) + possible_nulbyte = 1; + + parse_po_line(input_line, dstate, &entry, &pstate, col, &msgstr_index); if (entry && entry->msgid_plural) has_plural_msgid = 1; - switch(result) + switch (dstate->error) { - case PARSE_ERR_NONE: + case ERR_NONE: if (pstate & PS_MSGID) msgid_counter = -1; else if (msgid_counter != -1) @@ -487,13 +505,14 @@ load_file(struct DrawState* dstate, long* lineno, long* col) return 1; } break; - case PARSE_ERR_SYNTAX: - dstate->error = ERR_SYNTAX; - return 1; + case ERR_SYNTAX: + /* Allow for ERR_NOTPO_FILE to override ERR_SYNTAX */ + break; default: goto load_file_alloc_error; } - (*lineno)++; + if (!dstate->error) + (*lineno)++; } /* when the .po file is not a standard text file (no LF at end) */ @@ -1069,49 +1088,52 @@ save_file_cleanup: return result; } +#define INC_COL() \ + do \ + { \ + if (!dstate->error) \ + (*col)++; \ + } while (0) + int parse_po_line(const char* line, struct DrawState* dstate, struct PoEntry** current_entry, ParseState* pstate, long* col, int* msgstr_index) { - size_t i = 0; - *col = 1; const char* pline = line; - char token[MAXBUFLINE]; - *token = 0; - char* ptoken = token; + char* token = NULL; + char* ptoken = NULL; + size_t i = 0; + size_t len; assert(line != NULL); - /*if (!line) - return PARSE_ERR_INVAL;*/ - if (starts_with(line, "msgid ")) - { - size_t len = strlen("msgid "); - *pstate |= PS_MSGID; - pline += len; - *col += len; - } - else if (starts_with(line, "msgid_plural ")) + *col = 1; + + token = malloc(limits[L_LINE_MAX]); + if (!token) + goto parse_po_line_alloc_error; + *token = 0; + ptoken = token; + + if (starts_with(line, "msgid_plural")) { - size_t len = strlen("msgid_plural "); + len = strlen("msgid_plural"); *pstate &= ~PS_MSGID; *pstate |= PS_MSGID_PLURAL; pline += len; *col += len; } - else if (starts_with(line, "msgstr ")) + else if (starts_with(line, "msgid")) { - size_t len = strlen("msgstr "); - *pstate &= ~PS_MSGID; - *pstate &= ~PS_MSGID_PLURAL; - *pstate |= PS_MSGSTR; + len = strlen("msgid"); + *pstate |= PS_MSGID; pline += len; *col += len; } else if (starts_with(line, "msgstr[")) { - size_t len = strlen("msgstr["); + len = strlen("msgstr["); *pstate &= ~PS_MSGID; *pstate &= ~PS_MSGID_PLURAL; *pstate |= PS_MSGSTR_ARRAY; @@ -1131,7 +1153,19 @@ parse_po_line(const char* line, struct DrawState* dstate, (*col) += 2; } else - return PARSE_ERR_SYNTAX; + { + dstate->error = ERR_SYNTAX; + goto parse_po_cleanup; + } + } + else if (starts_with(line, "msgstr")) + { + len = strlen("msgstr"); + *pstate &= ~PS_MSGID; + *pstate &= ~PS_MSGID_PLURAL; + *pstate |= PS_MSGSTR; + pline += len; + *col += len; } else if (*line == 0) { @@ -1141,121 +1175,155 @@ parse_po_line(const char* line, struct DrawState* dstate, *pstate &= ~PS_MSGSTR_ARRAY; } else if (*line != ' ' && *line != '\t' && *line != '"' && *line != '#') - return PARSE_ERR_SYNTAX; + { + dstate->error = ERR_SYNTAX; + goto parse_po_cleanup; + } while (*pline && i < strlen(line)) { switch (*pline) { + case ' ': + case '\t': + if (*pstate & PS_COMMENT) + { + *ptoken++ = *pline++; + INC_COL(); + } + else if (*pstate & PS_STRING) + { + *ptoken++ = *pline++; + INC_COL(); + } + else + { + pline++; + INC_COL(); + } + break; case '#': - if (*col == 1) + if (pline - line + 1 == 1) { *pstate |= PS_COMMENT_TRAN; pline++; - (*col)++; + INC_COL(); } - else + else if (*pstate & (PS_STRING | PS_COMMENT)) { *ptoken++ = *pline++; - (*col)++; + INC_COL(); + } + else + { + dstate->error = ERR_SYNTAX; + goto parse_po_cleanup; } break; case '.': - if (*col == 2 && *pstate & PS_COMMENT_TRAN) + if (pline - line + 1 == 2 && *pstate & PS_COMMENT_TRAN) { *pstate &= ~PS_COMMENT_TRAN; *pstate |= PS_COMMENT_EXTR; pline++; - (*col)++; - if (*pline == ' ') - { - pline++; - (*col)++; - } - else if (*pline) - return PARSE_ERR_SYNTAX; + INC_COL(); } else { *ptoken++ = *pline++; - (*col)++; + INC_COL(); } break; case ',': - if (*col == 2 && *pstate & PS_COMMENT_TRAN) + if (pline - line + 1 == 2 && *pstate & PS_COMMENT_TRAN) { *pstate &= ~PS_COMMENT_TRAN; *pstate |= PS_COMMENT_FLAG; pline++; - (*col)++; - if (*pline == ' ') - { - pline++; - (*col)++; - } - else if (*pline) - return PARSE_ERR_SYNTAX; + INC_COL(); } else { *ptoken++ = *pline++; - (*col)++; + INC_COL(); } break; case ':': - if (*col == 2 && *pstate & PS_COMMENT_TRAN) + if (pline - line + 1 == 2 && *pstate & PS_COMMENT_TRAN) { *pstate &= ~PS_COMMENT_TRAN; *pstate |= PS_COMMENT_REFN; pline++; - (*col)++; - if (*pline == ' ') - { - pline++; - (*col)++; - } - else if (*pline) - return PARSE_ERR_SYNTAX; + INC_COL(); } else { *ptoken++ = *pline++; - (*col)++; + INC_COL(); } break; case '~': - if (*col == 2 && *pstate & PS_COMMENT_TRAN) + if (pline - line + 1 == 2 && (*pstate & PS_COMMENT_TRAN)) { *pstate &= ~PS_COMMENT_TRAN; *pstate |= PS_COMMENT_OBSO; pline++; - (*col)++; + INC_COL(); } else { *ptoken++ = *pline++; - (*col)++; + INC_COL(); + } + break; + case '\\': + if (*pstate & PS_COMMENT) + { + pline++; + INC_COL(); + } + else if ((*pstate & PS_STRING) && *(pstate + 1)) + { + *ptoken++ = *pline++; + *ptoken++ = *pline++; + INC_COL(); } break; case '"': - /* skip beginning and end quote */ - if ((ptoken == token || *(pline + 1) == 0) - && !(*pstate - & (PS_COMMENT_TRAN | PS_COMMENT_EXTR - | PS_COMMENT_OBSO))) + if (*pstate & PS_COMMENT) + { + pline++; + INC_COL(); + } + else if (*pstate & PS_STRING) { + *pstate &= ~PS_STRING; pline++; - (*col)++; + INC_COL(); } else { - *ptoken++ = *pline++; - (*col)++; + *pstate |= PS_STRING; + pline++; + INC_COL(); } break; default: - *ptoken++ = *pline++; - (*col)++; + if (*pstate & PS_COMMENT) + { + *ptoken++ = *pline++; + INC_COL(); + } + else if (*pstate & PS_STRING) + { + *ptoken++ = *pline++; + INC_COL(); + } + else + { + dstate->error = ERR_SYNTAX; + goto parse_po_cleanup; + } } } *ptoken = 0; @@ -1273,7 +1341,7 @@ parse_po_line(const char* line, struct DrawState* dstate, newchunk = realloc(dstate->entries, dstate->msgid_size * sizeof(struct PoEntry)); if (!newchunk) - return PARSE_ERR_ALLOC; + goto parse_po_line_alloc_error; dstate->entries = newchunk; *current_entry = dstate->entries + dstate->msgid_size - ALLOC_DELTA; @@ -1292,7 +1360,7 @@ parse_po_line(const char* line, struct DrawState* dstate, for (int i = 0; i < dstate->nplurals; i++) if (!set_msgstr(*current_entry, NULL, i, 0, dstate)) - return PARSE_ERR_ALLOC; + goto parse_po_line_alloc_error; } else if (*pstate & PS_MSGSTR) { @@ -1309,13 +1377,13 @@ parse_po_line(const char* line, struct DrawState* dstate, } if (!set_msgstr(*current_entry, token, 0, 1, dstate)) - return PARSE_ERR_ALLOC; + goto parse_po_line_alloc_error; } else if (*pstate & PS_MSGSTR_ARRAY) { if (!set_msgstr(*current_entry, token, *msgstr_index, 1, dstate)) - return PARSE_ERR_ALLOC; + goto parse_po_line_alloc_error; } else if (*pstate & PS_COMMENT_TRAN) { @@ -1323,7 +1391,7 @@ parse_po_line(const char* line, struct DrawState* dstate, size_t len = strlen(token) + 1; u32_token = calloc(len, sizeof(uint32_t)); if (!u32_token) - return PARSE_ERR_ALLOC; + goto parse_po_line_alloc_error; u8_string_to_unicode(u32_token, token, len); u32_add_comment(*current_entry, u32_token); free(u32_token); @@ -1334,7 +1402,7 @@ parse_po_line(const char* line, struct DrawState* dstate, size_t len = strlen(token) + 1; u32_token = calloc(len, sizeof(uint32_t)); if (!u32_token) - return PARSE_ERR_ALLOC; + goto parse_po_line_alloc_error; u8_string_to_unicode(u32_token, token, len); u32_add_trans_comment(*current_entry, u32_token); free(u32_token); @@ -1374,11 +1442,7 @@ parse_po_line(const char* line, struct DrawState* dstate, { /* Let this slip, ignore everything * instead; see po.h for rationale */ - /*return PARSE_ERR_SYNTAX;*/ - - while (*ptoken && *ptoken != ' ' - && *ptoken != ',' - && *ptoken != '\t') + while (*ptoken) ptoken++; } } @@ -1391,7 +1455,7 @@ parse_po_line(const char* line, struct DrawState* dstate, size_t len = strlen(token) + 1; u32_token = calloc(len, sizeof(uint32_t)); if (!u32_token) - return PARSE_ERR_ALLOC; + goto parse_po_line_alloc_error; u8_string_to_unicode(u32_token, token, len); if (u32_add_comment(*current_entry, u32_token)) (*current_entry)->obsolete = 1; @@ -1405,10 +1469,14 @@ parse_po_line(const char* line, struct DrawState* dstate, u32_add_trans_comment(*current_entry, (uint32_t*)L""); /* one-line states */ - *pstate &= ~(PS_COMMENT_TRAN | PS_COMMENT_EXTR | PS_COMMENT_FLAG - | PS_COMMENT_REFN | PS_COMMENT_OBSO); + *pstate &= ~PS_COMMENT; - return PARSE_ERR_NONE; +parse_po_cleanup: + return !!dstate->error; + +parse_po_line_alloc_error: + dstate->error = ERR_CANT_ALLOC; + return 1; } void diff --git a/po.h b/po.h @@ -11,11 +11,11 @@ /* If msgid/msgstr is longer, it will be saved as multiple lines */ #define SAVE_MULTILINE_TRESHOLD 70 -#define LOAD_PARSE_ERR(e) (LOAD_ERR_PARSE_PREFIX + (e)) +// #define LOAD_PARSE_ERR(e) (LOAD_ERR_PARSE_PREFIX + (e)) struct DrawState; -enum { +/*enum { PARSE_ERR_NONE = 0, PARSE_ERR_INVAL = 1, PARSE_ERR_ALLOC = 2, @@ -31,7 +31,7 @@ enum { LOAD_ERR_P_INVAL = LOAD_PARSE_ERR(PARSE_ERR_INVAL), LOAD_ERR_P_ALLOC = LOAD_PARSE_ERR(PARSE_ERR_ALLOC), LOAD_ERR_P_SYNTAX = LOAD_PARSE_ERR(PARSE_ERR_SYNTAX) -}; +};*/ enum { SAVE_ERR_NONE = 0, @@ -54,9 +54,13 @@ typedef enum { PS_MSGID = 1 << 5, PS_MSGID_PLURAL = 1 << 6, PS_MSGSTR = 1 << 7, - PS_MSGSTR_ARRAY = 1 << 8 + PS_MSGSTR_ARRAY = 1 << 8, + PS_STRING = 1 << 9 /* string literal */ } ParseState; +#define PS_COMMENT \ + ((PS_COMMENT_TRAN | PS_COMMENT_EXTR | PS_COMMENT_FLAG \ + | PS_COMMENT_REFN | PS_COMMENT_OBSO)) /* Complete list at: * * https://www.gnu.org/software/gettext/manual/html_node/PO-Files.html @@ -97,40 +101,35 @@ struct PoEntry { size_t ref_comment_lines; }; -char* format_flags(char* buffer, const size_t max, - const struct PoEntry* entry, const int draw_box); +char* format_flags(char* buffer, const size_t max, const struct PoEntry* entry, + const int draw_box); void init_po_entry(struct PoEntry* entry); void free_po_entry(struct PoEntry* entry); struct PoEntry* set_flags(struct PoEntry* entry, const Flags flags); -struct PoEntry* set_msgid(struct PoEntry* entry, const char* msgid, - int append); +struct PoEntry* set_msgid(struct PoEntry* entry, const char* msgid, int append); struct PoEntry* set_msgid_plural(struct PoEntry* entry, const char* msgid_plural, int append); struct PoEntry* set_plural_forms(struct PoEntry* entry, const char* plural_forms, int append); struct PoEntry* set_msgstr(struct PoEntry* entry, const char* msgstr, int msgstr_index, int append, struct DrawState* state); -struct PoEntry* u32_set_msgid(struct PoEntry* entry, - const uint32_t* msgid, size_t msgid_size, int append); +struct PoEntry* u32_set_msgid(struct PoEntry* entry, const uint32_t* msgid, + size_t msgid_size, int append); struct PoEntry* u32_set_msgid_plural(struct PoEntry* entry, const uint32_t* msgid_plural, size_t msgid_plural_size, int append); struct PoEntry* u32_set_plural_forms(struct PoEntry* entry, const uint32_t* plural_forms, int append); -struct PoEntry* u32_set_msgstr(struct PoEntry* entry, - const uint32_t* msgstr, int msgstr_index, int append, - struct DrawState* state); -struct PoEntry* u32_add_comment(struct PoEntry* entry, - const uint32_t* comment); +struct PoEntry* u32_set_msgstr(struct PoEntry* entry, const uint32_t* msgstr, + int msgstr_index, int append, struct DrawState* state); +struct PoEntry* u32_add_comment(struct PoEntry* entry, const uint32_t* comment); struct PoEntry* u32_add_trans_comment(struct PoEntry* entry, const uint32_t* trans_comment); -struct PoEntry* add_ref_comment(struct PoEntry* entry, - const char* comment); +struct PoEntry* add_ref_comment(struct PoEntry* entry, const char* comment); int load_file(struct DrawState* state, long* lineno, long* col); int save_file(struct DrawState* state); int parse_po_line(const char* line, struct DrawState* dstate, struct PoEntry** current_entry, ParseState* pstate, long* col, int* msgstr_index); -void update_statistics(const struct PoEntry* entries, - struct DrawState* state, struct PoEntry* entry); -void update_warning(const uint32_t* msgid, const uint32_t* msgstr, - int* warning); +void update_statistics(const struct PoEntry* entries, struct DrawState* state, + struct PoEntry* entry); +void update_warning(const uint32_t* msgid, const uint32_t* msgstr, int* warning); diff --git a/poe.c b/poe.c @@ -2670,10 +2670,19 @@ main_done: if (saved_errno) perror(PROGRAM_NAME); - if (state.error == ERR_NOTREG_FILE) + switch (state.error) + { + case ERR_NOTREG_FILE: print_error(1, errors[state.error], state.filename); - else if (state.error) + break; + case ERR_SYNTAX: + print_error(1, errors[state.error], state.filename, lineno, col); + break; + case ERR_NONE: + break; + default: print_error(1, errors[state.error]); + } return saved_errno ? saved_errno