From c7d142f82eac7b202e3d0d49ad93231976ad8173 Mon Sep 17 00:00:00 2001
From: Matej Habrnal <mhabrnal@redhat.com>
Date: Fri, 6 Mar 2015 00:12:47 +0100
Subject: [PATCH 2/2] reporter-mantisbt: introduce reporter-mantisbt

Signed-off-by: Matej Habrnal <mhabrnal@redhat.com>
---
 SOURCES/0127-lib-add-Problem-Format-API.patch      | 2244 +++++++++++++++++
 .../0128-bugzilla-port-to-Problem-Format-API.patch |  781 ++++++
 ...-lib-created-a-new-lib-file-for-reporters.patch |  405 +++
 ...0133-ureport-set-url-to-public-faf-server.patch |   27 +
 ...0134-conf-changed-URL-for-sending-uReport.patch |   29 +
 ...ntisbt-first-version-of-the-reporter-mant.patch | 2659 ++++++++++++++++++++
 SPECS/libreport.spec                               |   61 +-
 7 files changed, 6205 insertions(+), 1 deletion(-)
 create mode 100644 SOURCES/0127-lib-add-Problem-Format-API.patch
 create mode 100644 SOURCES/0128-bugzilla-port-to-Problem-Format-API.patch
 create mode 100644 SOURCES/0130-lib-created-a-new-lib-file-for-reporters.patch
 create mode 100644 SOURCES/0133-ureport-set-url-to-public-faf-server.patch
 create mode 100644 SOURCES/0134-conf-changed-URL-for-sending-uReport.patch
 create mode 100644 SOURCES/1000-reporter-mantisbt-first-version-of-the-reporter-mant.patch

diff --git a/SOURCES/0127-lib-add-Problem-Format-API.patch b/SOURCES/0127-lib-add-Problem-Format-API.patch
new file mode 100644
index 0000000..3c7ff9c
--- /dev/null
+++ b/SOURCES/0127-lib-add-Problem-Format-API.patch
@@ -0,0 +1,2244 @@
+From d18e0f667d33527c1c5e54f448f6aa2ba59dabdb Mon Sep 17 00:00:00 2001
+From: Jakub Filak <jfilak@redhat.com>
+Date: Thu, 4 Dec 2014 08:43:17 +0100
+Subject: [PATCH 127/136] lib: add Problem Format API
+
+Related to #303
+
+Signed-off-by: Jakub Filak <jfilak@redhat.com>
+---
+ po/POTFILES.in               |    1 +
+ src/include/Makefile.am      |    1 +
+ src/include/problem_report.h |  225 ++++++++
+ src/lib/Makefile.am          |    5 +-
+ src/lib/problem_report.c     | 1209 ++++++++++++++++++++++++++++++++++++++++++
+ tests/Makefile.am            |    3 +-
+ tests/problem_report.at      |  690 ++++++++++++++++++++++++
+ tests/testsuite.at           |    1 +
+ 8 files changed, 2133 insertions(+), 2 deletions(-)
+ create mode 100644 src/include/problem_report.h
+ create mode 100644 src/lib/problem_report.c
+ create mode 100644 tests/problem_report.at
+
+diff --git a/po/POTFILES.in b/po/POTFILES.in
+index 00046e2..c597b11 100644
+--- a/po/POTFILES.in
++++ b/po/POTFILES.in
+@@ -23,6 +23,7 @@ src/lib/ureport.c
+ src/lib/make_descr.c
+ src/lib/parse_options.c
+ src/lib/problem_data.c
++src/lib/problem_report.c
+ src/lib/run_event.c
+ src/plugins/abrt_rh_support.c
+ src/plugins/report_Bugzilla.xml.in
+diff --git a/src/include/Makefile.am b/src/include/Makefile.am
+index de44cda..47ba399 100644
+--- a/src/include/Makefile.am
++++ b/src/include/Makefile.am
+@@ -5,6 +5,7 @@ libreport_include_HEADERS = \
+     dump_dir.h \
+     event_config.h \
+     problem_data.h \
++    problem_report.h \
+     report.h \
+     run_event.h \
+     libreport_curl.h \
+diff --git a/src/include/problem_report.h b/src/include/problem_report.h
+new file mode 100644
+index 0000000..30781e6
+--- /dev/null
++++ b/src/include/problem_report.h
+@@ -0,0 +1,225 @@
++/*
++    Copyright (C) 2014  ABRT team
++    Copyright (C) 2014  RedHat Inc
++
++    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 2 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, write to the Free Software Foundation, Inc.,
++    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
++*/
++#ifndef LIBREPORT_PROBLEM_REPORT_H
++#define LIBREPORT_PROBLEM_REPORT_H
++
++#include <glib.h>
++#include <stdio.h>
++#include "problem_data.h"
++
++#ifdef __cplusplus
++extern "C" {
++#endif
++
++#define PR_SEC_SUMMARY "summary"
++#define PR_SEC_DESCRIPTION "description"
++
++/*
++ * The problem report structure represents a problem data formatted according
++ * to a format string.
++ *
++ * A problem report is composed of well-known sections:
++ *   - summary
++ *   - descritpion
++ *   - attach
++ *
++ * and custom sections accessed by:
++ *   problem_report_get_section();
++ */
++struct problem_report;
++typedef struct problem_report problem_report_t;
++
++/*
++ * Helpers for easily switching between FILE and struct strbuf
++ */
++
++/*
++ * Type of buffer used by Problem report
++ */
++typedef FILE problem_report_buffer;
++
++/*
++ * Wrapper for the proble buffer's formated output function.
++ */
++#define problem_report_buffer_printf(buf, fmt, ...)\
++    fprintf((buf), (fmt), ##__VA_ARGS__)
++
++
++/*
++ * Get a section buffer
++ *
++ * Use this function if you need to amend something to a formatted section.
++ *
++ * @param self Problem report
++ * @param section_name Name of required section
++ * @return Always valid pointer to a section buffer
++ */
++problem_report_buffer *problem_report_get_buffer(const problem_report_t *self,
++        const char *section_name);
++
++/*
++ * Get Summary string
++ *
++ * The returned pointer is valid as long as you perform no further output to
++ * the summary buffer.
++ *
++ * @param self Problem report
++ * @return Non-NULL pointer to summary data
++ */
++const char *problem_report_get_summary(const problem_report_t *self);
++
++/*
++ * Get Description string
++ *
++ * The returned pointer is valid as long as you perform no further output to
++ * the description buffer.
++ *
++ * @param self Problem report
++ * @return Non-NULL pointer to description data
++ */
++const char *problem_report_get_description(const problem_report_t *self);
++
++/*
++ * Get Section's string
++ *
++ * The returned pointer is valid as long as you perform no further output to
++ * the section's buffer.
++ *
++ * @param self Problem report
++ * @param section_name Name of the required section
++ * @return Non-NULL pointer to description data
++ */
++const char *problem_report_get_section(const problem_report_t *self,
++        const char *section_name);
++
++/*
++ * Get GList of the problem data items that are to be attached
++ *
++ * @param self Problem report
++ * @return A pointer to GList (NULL means empty list)
++ */
++GList *problem_report_get_attachments(const problem_report_t *self);
++
++/*
++ * Releases all resources allocated by a problem report
++ *
++ * @param self Problem report
++ */
++void problem_report_free(problem_report_t *self);
++
++
++/*
++ * An enum of Extra section flags
++ */
++enum problem_formatter_section_flags {
++    PFFF_REQUIRED = 1 << 0, ///< section must be present in the format spec
++};
++
++/*
++ * The problem formatter structure formats a problem data according to a format
++ * string and stores result a problem report.
++ *
++ * The problem formatter uses '%reason%' as %summary section format string, if
++ * %summary is not provided by a format string.
++ */
++struct problem_formatter;
++typedef struct problem_formatter problem_formatter_t;
++
++/*
++ * Constructs a new problem formatter.
++ *
++ * @return Non-NULL pointer to the new problem formatter
++ */
++problem_formatter_t *problem_formatter_new(void);
++
++/*
++ * Releases all resources allocated by a problem formatter
++ *
++ * @param self Problem formatter
++ */
++void problem_formatter_free(problem_formatter_t *self);
++
++/*
++ * Adds a new recognized section
++ *
++ * The problem formatter ignores a section in the format spec if the section is
++ * not one of the default nor added by this function.
++ *
++ * How the problem formatter handles these extra sections:
++ *
++ * A custom section is something like %description section. %description is the
++ * default section where all text (sub)sections are stored. If the formatter
++ * finds the custom section in format string, then starts storing text
++ * (sub)sections in the custom section.
++ *
++ * (%description)    |:: comment
++ * (%description)    |
++ * (%description)    |Package:: package
++ * (%description)    |
++ * (%additiona_info) |%additional_info::
++ * (%additiona_info) |%reporter%
++ * (%additiona_info) |User:: user_name,uid
++ * (%additiona_info) |
++ * (%additiona_info) |Directories:: root,cwd
++ *
++ *
++ * @param self Problem formatter
++ * @param name Name of the added section
++ * @param flags Info about the added section
++ * @return Zero on success. -EEXIST if the name is already known by the formatter
++ */
++int problem_formatter_add_section(problem_formatter_t *self, const char *name, int flags);
++
++/*
++ * Loads a problem format from a string.
++ *
++ * @param self Problem formatter
++ * @param fmt Format
++ * @return Zero on success or number of warnings (e.g. missing section,
++ * unrecognized section).
++ */
++int problem_formatter_load_string(problem_formatter_t* self, const char *fmt);
++
++
++/*
++ * Loads a problem format from a file.
++ *
++ * @param self Problem formatter
++ * @param pat Path to the format file
++ * @return Zero on success or number of warnings (e.g. missing section,
++ * unrecognized section).
++ */
++int problem_formatter_load_file(problem_formatter_t* self, const char *path);
++
++/*
++ * Creates a new problem report, formats the data according to the loaded
++ * format string and stores output in the report.
++ *
++ * @param self Problem formatter
++ * @param data Problem data to format
++ * @param report Pointer where the created problem report is to be stored
++ * @return Zero on success, otherwise non-zero value.
++ */
++int problem_formatter_generate_report(const problem_formatter_t *self, problem_data_t *data, problem_report_t **report);
++
++#ifdef __cplusplus
++}
++#endif
++
++#endif // LIBREPORT_PROBLEM_REPORT_H
+diff --git a/src/lib/Makefile.am b/src/lib/Makefile.am
+index 7d9722a..a0001ef 100644
+--- a/src/lib/Makefile.am
++++ b/src/lib/Makefile.am
+@@ -38,6 +38,7 @@ libreport_la_SOURCES = \
+     make_descr.c \
+     run_event.c \
+     problem_data.c \
++    problem_report.c \
+     create_dump_dir.c \
+     abrt_types.c \
+     parse_release.c \
+@@ -76,6 +77,7 @@ libreport_la_CPPFLAGS = \
+     $(GLIB_CFLAGS) \
+     $(GOBJECT_CFLAGS) \
+     $(AUGEAS_CFLAGS) \
++    $(SATYR_CFLAGS) \
+     -D_GNU_SOURCE
+ libreport_la_LDFLAGS = \
+     -version-info 0:1:0
+@@ -84,7 +86,8 @@ libreport_la_LIBADD = \
+     $(GLIB_LIBS) \
+     $(JOURNAL_LIBS) \
+     $(GOBJECT_LIBS) \
+-    $(AUGEAS_LIBS)
++    $(AUGEAS_LIBS) \
++    $(SATYR_LIBS)
+ 
+ libreportconfdir = $(CONF_DIR)
+ dist_libreportconf_DATA = \
+diff --git a/src/lib/problem_report.c b/src/lib/problem_report.c
+new file mode 100644
+index 0000000..0afc1ca
+--- /dev/null
++++ b/src/lib/problem_report.c
+@@ -0,0 +1,1209 @@
++/*
++    Copyright (C) 2014  ABRT team
++    Copyright (C) 2014  RedHat Inc
++
++    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 2 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, write to the Free Software Foundation, Inc.,
++    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
++*/
++
++#include "problem_report.h"
++#include "internal_libreport.h"
++
++#include <satyr/stacktrace.h>
++#include <satyr/abrt.h>
++
++#include <assert.h>
++
++#define DESTROYED_POINTER (void *)0xdeadbeef
++
++/* FORMAT:
++ * |%summary:: Hello, world
++ * |Problem description:: %bare_comment
++ * |
++ * |Package:: package
++ * |
++ * |%attach: %binary, backtrace
++ * |
++ * |%additional_info::
++ * |%reporter%
++ * |User:: user_name,uid
++ * |
++ * |Directories:: root,cwd
++ *
++ * PARSED DATA (list of struct section_t):
++ * {
++ *   section_t {
++ *      .name     = '%summary';
++ *      .items    = { 'Hello, world' };
++ *      .children = NULL;
++ *   },
++ *   section_t {
++ *      .name     = '%attach'
++ *      .items    = { '%binary', 'backtrace' };
++ *      .children = NULL;
++ *   },
++ *   section_t {
++ *      .name     = '%description'
++ *      .items    = NULL;
++ *      .children = {
++ *        section_t {
++ *          .name     = 'Problem description:';
++ *          .items    = { '%bare_comment' };
++ *          .children = NULL;
++ *        },
++ *        section_t {
++ *          .name     = '';
++ *          .items    = NULL;
++ *          .children = NULL;
++ *        },
++ *        section_t {
++ *          .name     = 'Package:';
++ *          .items    = { 'package' };
++ *          .children = NULL;
++ *        },
++ *      }
++ *   },
++ *   section_t {
++ *      .name     = '%additional_info'
++ *      .items    = { '%reporter%' };
++ *      .children = {
++ *        section_t {
++ *          .name     = 'User:';
++ *          .items    = { 'user_name', 'uid' };
++ *          .children = NULL;
++ *        },
++ *        section_t {
++ *          .name     = '';
++ *          .items    = NULL;
++ *          .children = NULL;
++ *        },
++ *        section_t {
++ *          .name     = 'Directories:';
++ *          .items    = { 'root', 'cwd' };
++ *          .children = NULL;
++ *        },
++ *      }
++ *   }
++ * }
++ */
++struct section_t {
++    char *name;      ///< name or output text (%summar, 'Package version:');
++    GList *items;    ///< list of file names and special items (%reporter, %binar, ...)
++    GList *children; ///< list of sub sections (struct section_t)
++};
++
++typedef struct section_t section_t;
++
++static section_t *
++section_new(const char *name)
++{
++    section_t *self = xmalloc(sizeof(*self));
++    self->name = xstrdup(name);
++    self->items = NULL;
++    self->children = NULL;
++
++    return self;
++}
++
++static void
++section_free(section_t *self)
++{
++    if (self == NULL)
++        return;
++
++    free(self->name);
++    g_list_free_full(self->items, free);
++    g_list_free_full(self->children, (GDestroyNotify)section_free);
++
++    free(self);
++}
++
++static int
++section_name_cmp(section_t *lhs, const char *rhs)
++{
++    return strcmp((lhs->name + 1), rhs);
++}
++
++/* Utility functions */
++
++static GList*
++split_string_on_char(const char *str, char ch)
++{
++    GList *list = NULL;
++    for (;;)
++    {
++        const char *delim = strchrnul(str, ch);
++        list = g_list_prepend(list, xstrndup(str, delim - str));
++        if (*delim == '\0')
++            break;
++        str = delim + 1;
++    }
++    return g_list_reverse(list);
++}
++
++static int
++compare_item_name(const char *lookup, const char *name)
++{
++    if (lookup[0] == '-')
++        lookup++;
++    else if (strncmp(lookup, "%bare_", 6) == 0)
++        lookup += 6;
++    return strcmp(lookup, name);
++}
++
++static int
++is_item_name_in_section(const section_t *lookup, const char *name)
++{
++    if (g_list_find_custom(lookup->items, name, (GCompareFunc)compare_item_name))
++        return 0; /* "found it!" */
++    return 1;
++}
++
++static bool is_explicit_or_forbidden(const char *name, GList *comment_fmt_spec);
++
++static int
++is_explicit_or_forbidden_child(const section_t *master_section, const char *name)
++{
++    if (is_explicit_or_forbidden(name, master_section->children))
++        return 0; /* "found it!" */
++    return 1;
++}
++
++/* For example: 'package' belongs to '%oneline', but 'package' is used in
++ * 'Version of component', so it is not very helpful to include that file once
++ * more in another section
++ */
++static bool
++is_explicit_or_forbidden(const char *name, GList *comment_fmt_spec)
++{
++    return    g_list_find_custom(comment_fmt_spec, name, (GCompareFunc)is_item_name_in_section)
++           || g_list_find_custom(comment_fmt_spec, name, (GCompareFunc)is_explicit_or_forbidden_child);
++}
++
++static GList*
++load_stream(FILE *fp)
++{
++    assert(fp);
++
++    GList *sections = NULL;
++    section_t *master = section_new("%description");
++    section_t *sec = NULL;
++
++    sections = g_list_append(sections, master);
++
++    char *line;
++    while ((line = xmalloc_fgetline(fp)) != NULL)
++    {
++        /* Skip comments */
++        char first = *skip_whitespace(line);
++        if (first == '#')
++            goto free_line;
++
++        /* Handle trailing backslash continuation */
++ check_continuation: ;
++        unsigned len = strlen(line);
++        if (len && line[len-1] == '\\')
++        {
++            line[len-1] = '\0';
++            char *next_line = xmalloc_fgetline(fp);
++            if (next_line)
++            {
++                line = append_to_malloced_string(line, next_line);
++                free(next_line);
++                goto check_continuation;
++            }
++        }
++
++        /* We are reusing line buffer to form temporary
++         * "key\0values\0..." in its beginning
++         */
++        bool summary_line = false;
++        char *value = NULL;
++        char *src;
++        char *dst;
++        for (src = dst = line; *src; src++)
++        {
++            char c = *src;
++            /* did we reach the value list? */
++            if (!value && c == ':' && src[1] == ':')
++            {
++                *dst++ = '\0'; /* terminate key */
++                src += 1;
++                value = dst; /* remember where value starts */
++                summary_line = (strcmp(line, "%summary") == 0);
++                if (summary_line)
++                {
++                    value = (src + 1);
++                    break;
++                }
++                continue;
++            }
++            /* skip whitespace in value list */
++            if (value && isspace(c))
++                continue;
++            *dst++ = c; /* store next key or value char */
++        }
++
++        GList *item_list = NULL;
++        if (summary_line)
++        {
++            /* %summary is special */
++            item_list = g_list_append(NULL, xstrdup(skip_whitespace(value)));
++        }
++        else
++        {
++            *dst = '\0'; /* terminate value (or key) */
++            if (value)
++                item_list = split_string_on_char(value, ',');
++        }
++
++        sec = section_new(line);
++        sec->items = item_list;
++
++        if (sec->name[0] == '%')
++        {
++            if (!summary_line && strcmp(sec->name, "%attach") != 0)
++            {
++                master->children = g_list_reverse(master->children);
++                master = sec;
++            }
++
++            sections = g_list_prepend(sections, sec);
++        }
++        else
++            master->children = g_list_prepend(master->children, sec);
++
++ free_line:
++        free(line);
++    }
++
++    /* If master equals sec, then master's children list was not yet reversed.
++     *
++     * %description is the default section (i.e is not explicitly mentioned)
++     * and %summary nor %attach cause its children list to reverse.
++     */
++    if (master == sec || strcmp(master->name, "%description") == 0)
++        master->children = g_list_reverse(master->children);
++
++    return sections;
++}
++
++
++/* Summary generation */
++
++#define MAX_OPT_DEPTH 10
++static int
++format_percented_string(const char *str, problem_data_t *pd, FILE *result)
++{
++    long old_pos[MAX_OPT_DEPTH] = { 0 };
++    int okay[MAX_OPT_DEPTH] = { 1 };
++    long len = 0;
++    int opt_depth = 1;
++
++    while (*str) {
++        switch (*str) {
++        default:
++            putc(*str, result);
++            len++;
++            str++;
++            break;
++        case '\\':
++            if (str[1])
++                str++;
++            putc(*str, result);
++            len++;
++            str++;
++            break;
++        case '[':
++            if (str[1] == '[' && opt_depth < MAX_OPT_DEPTH)
++            {
++                old_pos[opt_depth] = len;
++                okay[opt_depth] = 1;
++                opt_depth++;
++                str += 2;
++            } else {
++                putc(*str, result);
++                len++;
++                str++;
++            }
++            break;
++        case ']':
++            if (str[1] == ']' && opt_depth > 1)
++            {
++                opt_depth--;
++                if (!okay[opt_depth])
++                {
++                    fseek(result, old_pos[opt_depth], SEEK_SET);
++                    len = old_pos[opt_depth];
++                }
++                str += 2;
++            } else {
++                putc(*str, result);
++                len++;
++                str++;
++            }
++            break;
++        case '%': ;
++            char *nextpercent = strchr(++str, '%');
++            if (!nextpercent)
++            {
++                error_msg_and_die("Unterminated %%element%%: '%s'", str - 1);
++            }
++
++            *nextpercent = '\0';
++            const problem_item *item = problem_data_get_item_or_NULL(pd, str);
++            *nextpercent = '%';
++
++            if (item && (item->flags & CD_FLAG_TXT))
++            {
++                fputs(item->content, result);
++                len += strlen(item->content);
++            }
++            else
++                okay[opt_depth - 1] = 0;
++            str = nextpercent + 1;
++            break;
++        }
++    }
++
++    if (opt_depth > 1)
++    {
++        error_msg_and_die("Unbalanced [[ ]] bracket");
++    }
++
++    if (!okay[0])
++    {
++        error_msg("Undefined variable outside of [[ ]] bracket");
++    }
++
++    return 0;
++}
++
++/* BZ comment generation */
++
++static int
++append_text(struct strbuf *result, const char *item_name, const char *content, bool print_item_name)
++{
++    char *eol = strchrnul(content, '\n');
++    if (eol[0] == '\0' || eol[1] == '\0')
++    {
++        /* one-liner */
++        int pad = 16 - (strlen(item_name) + 2);
++        if (pad < 0)
++            pad = 0;
++        if (print_item_name)
++            strbuf_append_strf(result,
++                    eol[0] == '\0' ? "%s: %*s%s\n" : "%s: %*s%s",
++                    item_name, pad, "", content
++            );
++        else
++            strbuf_append_strf(result,
++                    eol[0] == '\0' ? "%s\n" : "%s",
++                    content
++            );
++    }
++    else
++    {
++        /* multi-line item */
++        if (print_item_name)
++            strbuf_append_strf(result, "%s:\n", item_name);
++        for (;;)
++        {
++            eol = strchrnul(content, '\n');
++            strbuf_append_strf(result,
++                    /* For %bare_multiline_item, we don't want to print colons */
++                    (print_item_name ? ":%.*s\n" : "%.*s\n"),
++                    (int)(eol - content), content
++            );
++            if (eol[0] == '\0' || eol[1] == '\0')
++                break;
++            content = eol + 1;
++        }
++    }
++    return 1;
++}
++
++static int
++append_short_backtrace(struct strbuf *result, problem_data_t *problem_data, size_t max_text_size, bool print_item_name)
++{
++    const problem_item *item = problem_data_get_item_or_NULL(problem_data,
++                                                             FILENAME_BACKTRACE);
++    if (!item)
++        return 0; /* "I did not print anything" */
++    if (!(item->flags & CD_FLAG_TXT))
++        return 0; /* "I did not print anything" */
++
++    char *truncated = NULL;
++
++    if (strlen(item->content) >= max_text_size)
++    {
++        log_debug("'backtrace' exceeds the text file size, going to append its short version");
++
++        char *error_msg = NULL;
++        const char *analyzer = problem_data_get_content_or_NULL(problem_data, FILENAME_ANALYZER);
++        if (!analyzer)
++        {
++            log_debug("Problem data does not contain '"FILENAME_ANALYZER"' file");
++            return 0;
++        }
++
++        /* For CCpp crashes, use the GDB-produced backtrace which should be
++         * available by now. sr_abrt_type_from_analyzer returns SR_REPORT_CORE
++         * by default for CCpp crashes.
++         */
++        enum sr_report_type report_type = sr_abrt_type_from_analyzer(analyzer);
++        if (strcmp(analyzer, "CCpp") == 0)
++        {
++            log_debug("Successfully identified 'CCpp' abrt type");
++            report_type = SR_REPORT_GDB;
++        }
++
++        struct sr_stacktrace *backtrace = sr_stacktrace_parse(report_type,
++                item->content, &error_msg);
++
++        if (!backtrace)
++        {
++            log(_("Can't parse backtrace: %s"), error_msg);
++            free(error_msg);
++            return 0;
++        }
++
++        /* Get optimized thread stack trace for 10 top most frames */
++        truncated = sr_stacktrace_to_short_text(backtrace, 10);
++        sr_stacktrace_free(backtrace);
++
++        if (!truncated)
++        {
++            log(_("Can't generate stacktrace description (no crash thread?)"));
++            return 0;
++        }
++    }
++    else
++    {
++        log_debug("'backtrace' is small enough to be included as is");
++    }
++
++    append_text(result,
++                /*item_name:*/ truncated ? "truncated_backtrace" : FILENAME_BACKTRACE,
++                /*content:*/   truncated ? truncated             : item->content,
++                print_item_name
++    );
++    free(truncated);
++    return 1;
++}
++
++static int
++append_item(struct strbuf *result, const char *item_name, problem_data_t *pd, GList *comment_fmt_spec)
++{
++    bool print_item_name = (strncmp(item_name, "%bare_", strlen("%bare_")) != 0);
++    if (!print_item_name)
++        item_name += strlen("%bare_");
++
++    if (item_name[0] != '%')
++    {
++        struct problem_item *item = problem_data_get_item_or_NULL(pd, item_name);
++        if (!item)
++            return 0; /* "I did not print anything" */
++        if (!(item->flags & CD_FLAG_TXT))
++            return 0; /* "I did not print anything" */
++
++        char *formatted = problem_item_format(item);
++        char *content = formatted ? formatted : item->content;
++        append_text(result, item_name, content, print_item_name);
++        free(formatted);
++        return 1; /* "I printed something" */
++    }
++
++    /* Special item name */
++
++    /* Compat with previously-existed ad-hockery: %short_backtrace */
++    if (strcmp(item_name, "%short_backtrace") == 0)
++        return append_short_backtrace(result, pd, CD_TEXT_ATT_SIZE_BZ, print_item_name);
++
++    /* Compat with previously-existed ad-hockery: %reporter */
++    if (strcmp(item_name, "%reporter") == 0)
++        return append_text(result, "reporter", PACKAGE"-"VERSION, print_item_name);
++
++    /* %oneline,%multiline,%text */
++    bool oneline   = (strcmp(item_name+1, "oneline"  ) == 0);
++    bool multiline = (strcmp(item_name+1, "multiline") == 0);
++    bool text      = (strcmp(item_name+1, "text"     ) == 0);
++    if (!oneline && !multiline && !text)
++    {
++        log("Unknown or unsupported element specifier '%s'", item_name);
++        return 0; /* "I did not print anything" */
++    }
++
++    int printed = 0;
++
++    /* Iterate over _sorted_ items */
++    GList *sorted_names = g_hash_table_get_keys(pd);
++    sorted_names = g_list_sort(sorted_names, (GCompareFunc)strcmp);
++
++    /* %text => do as if %oneline, then repeat as if %multiline */
++    if (text)
++        oneline = 1;
++
++ again: ;
++    GList *l = sorted_names;
++    while (l)
++    {
++        const char *name = l->data;
++        l = l->next;
++        struct problem_item *item = g_hash_table_lookup(pd, name);
++        if (!item)
++            continue; /* paranoia, won't happen */
++
++        if (!(item->flags & CD_FLAG_TXT))
++            continue;
++
++        if (is_explicit_or_forbidden(name, comment_fmt_spec))
++            continue;
++
++        char *formatted = problem_item_format(item);
++        char *content = formatted ? formatted : item->content;
++        char *eol = strchrnul(content, '\n');
++        bool is_oneline = (eol[0] == '\0' || eol[1] == '\0');
++        if (oneline == is_oneline)
++            printed |= append_text(result, name, content, print_item_name);
++        free(formatted);
++    }
++    if (text && oneline)
++    {
++        /* %text, and we just did %oneline. Repeat as if %multiline */
++        oneline = 0;
++        /*multiline = 1; - not checked in fact, so why bother setting? */
++        goto again;
++    }
++
++    g_list_free(sorted_names); /* names themselves are not freed */
++
++    return printed;
++}
++
++#define add_to_section_output(format, ...) \
++    do { \
++    for (; empty_lines > 0; --empty_lines) fputc('\n', result); \
++    empty_lines = 0; \
++    fprintf(result, format, __VA_ARGS__); \
++    } while (0)
++
++static void
++format_section(section_t *section, problem_data_t *pd, GList *comment_fmt_spec, FILE *result)
++{
++    int empty_lines = -1;
++
++    for (GList *iter = section->children; iter; iter = g_list_next(iter))
++    {
++        section_t *child = (section_t *)iter->data;
++        if (child->items)
++        {
++            /* "Text: item[,item]..." */
++            struct strbuf *output = strbuf_new();
++            GList *item = child->items;
++            while (item)
++            {
++                const char *str = item->data;
++                item = item->next;
++                if (str[0] == '-') /* "-name", ignore it */
++                    continue;
++                append_item(output, str, pd, comment_fmt_spec);
++            }
++
++            if (output->len != 0)
++                add_to_section_output((child->name[0] ? "%s:\n%s" : "%s%s"),
++                                      child->name, output->buf);
++
++            strbuf_free(output);
++        }
++        else
++        {
++            /* Just "Text" (can be "") */
++
++            /* Filter out trailint empty lines */
++            if (child->name[0] != '\0')
++                add_to_section_output("%s\n", child->name);
++            /* Do not count empty lines, if output wasn't yet produced */
++            else if (empty_lines >= 0)
++                ++empty_lines;
++        }
++    }
++}
++
++static GList *
++get_special_items(const char *item_name, problem_data_t *pd, GList *comment_fmt_spec)
++{
++    /* %oneline,%multiline,%text,%binary */
++    bool oneline   = (strcmp(item_name+1, "oneline"  ) == 0);
++    bool multiline = (strcmp(item_name+1, "multiline") == 0);
++    bool text      = (strcmp(item_name+1, "text"     ) == 0);
++    bool binary    = (strcmp(item_name+1, "binary"   ) == 0);
++    if (!oneline && !multiline && !text && !binary)
++    {
++        log("Unknown or unsupported element specifier '%s'", item_name);
++        return NULL;
++    }
++
++    log_debug("Special item_name '%s', iterating for attach...", item_name);
++    GList *result = 0;
++
++    /* Iterate over _sorted_ items */
++    GList *sorted_names = g_hash_table_get_keys(pd);
++    sorted_names = g_list_sort(sorted_names, (GCompareFunc)strcmp);
++
++    GList *l = sorted_names;
++    while (l)
++    {
++        const char *name = l->data;
++        l = l->next;
++        struct problem_item *item = g_hash_table_lookup(pd, name);
++        if (!item)
++            continue; /* paranoia, won't happen */
++
++        if (is_explicit_or_forbidden(name, comment_fmt_spec))
++            continue;
++
++        if ((item->flags & CD_FLAG_TXT) && !binary)
++        {
++            char *content = item->content;
++            char *eol = strchrnul(content, '\n');
++            bool is_oneline = (eol[0] == '\0' || eol[1] == '\0');
++            if (text || oneline == is_oneline)
++                result = g_list_append(result, xstrdup(name));
++        }
++        else if ((item->flags & CD_FLAG_BIN) && binary)
++            result = g_list_append(result, xstrdup(name));
++    }
++
++    g_list_free(sorted_names); /* names themselves are not freed */
++
++
++    log_debug("...Done iterating over '%s' for attach", item_name);
++
++    return result;
++}
++
++static GList *
++get_attached_files(problem_data_t *pd, GList *items, GList *comment_fmt_spec)
++{
++    GList *result = NULL;
++    GList *item = items;
++    while (item != NULL)
++    {
++        const char *item_name = item->data;
++        item = item->next;
++        if (item_name[0] == '-') /* "-name", ignore it */
++            continue;
++
++        if (item_name[0] != '%')
++        {
++            result = g_list_append(result, xstrdup(item_name));
++            continue;
++        }
++
++        GList *special = get_special_items(item_name, pd, comment_fmt_spec);
++        if (special == NULL)
++        {
++            log_notice("No attachment found for '%s'", item_name);
++            continue;
++        }
++
++        result = g_list_concat(result, special);
++    }
++
++    return result;
++}
++
++/*
++ * Problem Report - memor stream
++ *
++ * A wrapper for POSIX memory stream.
++ *
++ * A memory stream is presented as FILE *.
++ *
++ * A memory stream is associated with a pointer to written data and a pointer
++ * to size of the written data.
++ *
++ * This structure holds all of the used pointers.
++ */
++struct memstream_buffer
++{
++    char *msb_buffer;
++    size_t msb_size;
++    FILE *msb_stream;
++};
++
++static struct memstream_buffer *
++memstream_buffer_new()
++{
++    struct memstream_buffer *self = xmalloc(sizeof(*self));
++
++    self->msb_buffer = NULL;
++    self->msb_stream = open_memstream(&(self->msb_buffer), &(self->msb_size));
++
++    return self;
++}
++
++static void
++memstream_buffer_free(struct memstream_buffer *self)
++{
++    if (self == NULL)
++        return;
++
++    fclose(self->msb_stream);
++    self->msb_stream = DESTROYED_POINTER;
++
++    free(self->msb_buffer);
++    self->msb_buffer = DESTROYED_POINTER;
++
++    free(self);
++}
++
++static FILE *
++memstream_get_stream(struct memstream_buffer *self)
++{
++    assert(self != NULL);
++
++    return self->msb_stream;
++}
++
++static const char *
++memstream_get_string(struct memstream_buffer *self)
++{
++    assert(self != NULL);
++    assert(self->msb_stream != NULL);
++
++    fflush(self->msb_stream);
++
++    return self->msb_buffer;
++}
++
++
++/*
++ * Problem Report
++ *
++ * The formated strings are internaly stored in "buffer"s. If a programer wants
++ * to get a formated section data, a getter function extracts those data from
++ * the apropriate buffer and returns them in form of null-terminated string.
++ *
++ * Each section has own buffer.
++ *
++ * There are three common sections that are always present:
++ * 1. summary
++ * 2. description
++ * 3. attach
++ * Buffers of these sections has own structure member for the sake of
++ * efficiency.
++ *
++ * The custom sections hash their buffers stored in a map where key is a
++ * section's name and value is a section's buffer.
++ *
++ * Problem report provides the programers with the possibility to ammend
++ * formated output to any section buffer.
++ */
++struct problem_report
++{
++    struct memstream_buffer *pr_sec_summ; ///< %summary buffer
++    struct memstream_buffer *pr_sec_desc; ///< %description buffer
++    GList            *pr_attachments;     ///< %attach - list of file names
++    GHashTable       *pr_sec_custom;      ///< map : %(custom section) -> buffer
++};
++
++static problem_report_t *
++problem_report_new()
++{
++    problem_report_t *self = xmalloc(sizeof(*self));
++
++    self->pr_sec_summ = memstream_buffer_new();
++    self->pr_sec_desc = memstream_buffer_new();
++    self->pr_attachments = NULL;
++    self->pr_sec_custom = NULL;
++
++    return self;
++}
++
++static void
++problem_report_initialize_custom_sections(problem_report_t *self)
++{
++    assert(self != NULL);
++    assert(self->pr_sec_custom == NULL);
++
++    self->pr_sec_custom = g_hash_table_new_full(g_str_hash, g_str_equal, free,
++                                                (GDestroyNotify)memstream_buffer_free);
++}
++
++static void
++problem_report_destroy_custom_sections(problem_report_t *self)
++{
++    assert(self != NULL);
++    assert(self->pr_sec_custom != NULL);
++
++    g_hash_table_destroy(self->pr_sec_custom);
++}
++
++static int
++problem_report_add_custom_section(problem_report_t *self, const char *name)
++{
++    assert(self != NULL);
++
++    if (self->pr_sec_custom == NULL)
++    {
++        problem_report_initialize_custom_sections(self);
++    }
++
++    if (problem_report_get_buffer(self, name))
++    {
++        log_warning("Custom section already exists : '%s'", name);
++        return -EEXIST;
++    }
++
++    log_debug("Problem report enriched with section : '%s'", name);
++    g_hash_table_insert(self->pr_sec_custom, xstrdup(name), memstream_buffer_new());
++    return 0;
++}
++
++static struct memstream_buffer *
++problem_report_get_section_buffer(const problem_report_t *self, const char *section_name)
++{
++    if (self->pr_sec_custom == NULL)
++    {
++        log_debug("Couldn't find section '%s': no custom section added", section_name);
++        return NULL;
++    }
++
++    return (struct memstream_buffer *)g_hash_table_lookup(self->pr_sec_custom, section_name);
++}
++
++problem_report_buffer *
++problem_report_get_buffer(const problem_report_t *self, const char *section_name)
++{
++    assert(self != NULL);
++    assert(section_name != NULL);
++
++    if (strcmp(PR_SEC_SUMMARY, section_name) == 0)
++        return memstream_get_stream(self->pr_sec_summ);
++
++    if (strcmp(PR_SEC_DESCRIPTION, section_name) == 0)
++        return memstream_get_stream(self->pr_sec_desc);
++
++    struct memstream_buffer *buf = problem_report_get_section_buffer(self, section_name);
++    return buf == NULL ? NULL : memstream_get_stream(buf);
++}
++
++const char *
++problem_report_get_summary(const problem_report_t *self)
++{
++    assert(self != NULL);
++
++    return memstream_get_string(self->pr_sec_summ);
++}
++
++const char *
++problem_report_get_description(const problem_report_t *self)
++{
++    assert(self != NULL);
++
++    return memstream_get_string(self->pr_sec_desc);
++}
++
++const char *
++problem_report_get_section(const problem_report_t *self, const char *section_name)
++{
++    assert(self != NULL);
++    assert(section_name);
++
++    struct memstream_buffer *buf = problem_report_get_section_buffer(self, section_name);
++
++    if (buf == NULL)
++        return NULL;
++
++    return memstream_get_string(buf);
++}
++
++static void
++problem_report_set_attachments(problem_report_t *self, GList *attachments)
++{
++    assert(self != NULL);
++    assert(self->pr_attachments == NULL);
++
++    self->pr_attachments = attachments;
++}
++
++GList *
++problem_report_get_attachments(const problem_report_t *self)
++{
++    assert(self != NULL);
++
++    return self->pr_attachments;
++}
++
++void
++problem_report_free(problem_report_t *self)
++{
++    if (self == NULL)
++        return;
++
++    memstream_buffer_free(self->pr_sec_summ);
++    self->pr_sec_summ = DESTROYED_POINTER;
++
++    memstream_buffer_free(self->pr_sec_desc);
++    self->pr_sec_desc = DESTROYED_POINTER;
++
++    g_list_free_full(self->pr_attachments, free);
++    self->pr_attachments = DESTROYED_POINTER;
++
++    if (self->pr_sec_custom)
++    {
++        problem_report_destroy_custom_sections(self);
++        self->pr_sec_custom = DESTROYED_POINTER;
++    }
++
++    free(self);
++}
++
++/*
++ * Problem Formatter - extra section
++ */
++struct extra_section
++{
++    char *pfes_name;    ///< name with % prefix
++    int   pfes_flags;   ///< whether is required or not
++};
++
++static struct extra_section *
++extra_section_new(const char *name, int flags)
++{
++    struct extra_section *self = xmalloc(sizeof(*self));
++
++    self->pfes_name = xstrdup(name);
++    self->pfes_flags = flags;
++
++    return self;
++}
++
++static void
++extra_section_free(struct extra_section *self)
++{
++    if (self == NULL)
++        return;
++
++    free(self->pfes_name);
++    self->pfes_name = DESTROYED_POINTER;
++
++    free(self);
++}
++
++static int
++extra_section_name_cmp(struct extra_section *lhs, const char *rhs)
++{
++    return strcmp(lhs->pfes_name, rhs);
++}
++
++/*
++ * Problem Formatter
++ *
++ * Holds parsed sections lists.
++ */
++struct problem_formatter
++{
++    GList *pf_sections;         ///< parsed sections (struct section_t)
++    GList *pf_extra_sections;   ///< user configured sections (struct extra_section)
++    char  *pf_default_summary;  ///< default summary format
++};
++
++problem_formatter_t *
++problem_formatter_new(void)
++{
++    problem_formatter_t *self = xzalloc(sizeof(*self));
++
++    self->pf_default_summary = xstrdup("%reason%");
++
++    return self;
++}
++
++void
++problem_formatter_free(problem_formatter_t *self)
++{
++    if (self == NULL)
++        return;
++
++    g_list_free_full(self->pf_sections, (GDestroyNotify)section_free);
++    self->pf_sections = DESTROYED_POINTER;
++
++    g_list_free_full(self->pf_extra_sections, (GDestroyNotify)extra_section_free);
++    self->pf_extra_sections = DESTROYED_POINTER;
++
++    free(self->pf_default_summary);
++    self->pf_default_summary = DESTROYED_POINTER;
++
++    free(self);
++}
++
++static int
++problem_formatter_is_section_known(problem_formatter_t *self, const char *name)
++{
++  return    strcmp(name, "summary")     == 0
++         || strcmp(name, "attach")      == 0
++         || strcmp(name, "description") == 0
++         || NULL != g_list_find_custom(self->pf_extra_sections, name, (GCompareFunc)extra_section_name_cmp);
++}
++
++// i.e additional_info -> no flags
++int
++problem_formatter_add_section(problem_formatter_t *self, const char *name, int flags)
++{
++    /* Do not add already added sections */
++    if (problem_formatter_is_section_known(self, name))
++    {
++        log_debug("Extra section already exists : '%s' ", name);
++        return -EEXIST;
++    }
++
++    self->pf_extra_sections = g_list_prepend(self->pf_extra_sections,
++                                             extra_section_new(name, flags));
++
++    return 0;
++}
++
++// check format validity and produce warnings
++static int
++problem_formatter_validate(problem_formatter_t *self)
++{
++    int retval = 0;
++
++    /* Go through all (struct extra_section)s and check whete those having flag
++     * PFFF_REQUIRED are present in the parsed (struct section_t)s.
++     */
++    for (GList *iter = self->pf_extra_sections; iter; iter = g_list_next(iter))
++    {
++        struct extra_section *section = (struct extra_section *)iter->data;
++
++        log_debug("Validating extra section : '%s'", section->pfes_name);
++
++        if (   (PFFF_REQUIRED & section->pfes_flags)
++            && NULL == g_list_find_custom(self->pf_sections, section->pfes_name, (GCompareFunc)section_name_cmp))
++        {
++            log_warning("Problem format misses required section : '%s'", section->pfes_name);
++            ++retval;
++        }
++    }
++
++    /* Go through all the parsed (struct section_t)s check whether are all
++     * known, i.e. each section is either one of the common sections (summary,
++     * description, attach) or is present in the (struct extra_section)s.
++     */
++    for (GList *iter = self->pf_sections; iter; iter = g_list_next(iter))
++    {
++        section_t *section = (section_t *)iter->data;
++
++        if (!problem_formatter_is_section_known(self, (section->name + 1)))
++        {
++            log_warning("Problem format contains unrecognized section : '%s'", section->name);
++            ++retval;
++        }
++    }
++
++    return retval;
++}
++
++int
++problem_formatter_load_string(problem_formatter_t *self, const char *fmt)
++{
++    const size_t len = strlen(fmt);
++    if (len != 0)
++    {
++        FILE *fp = fmemopen((void *)fmt, len, "r");
++        if (fp == NULL)
++        {
++            error_msg("Not enough memory to open a stream for reading format string.");
++            return -ENOMEM;
++        }
++
++        self->pf_sections = load_stream(fp);
++        fclose(fp);
++    }
++
++    return problem_formatter_validate(self);
++}
++
++int
++problem_formatter_load_file(problem_formatter_t *self, const char *path)
++{
++    FILE *fp = stdin;
++    if (strcmp(path, "-") != 0)
++    {
++        fp = fopen(path, "r");
++        if (!fp)
++            return -ENOENT;
++    }
++
++    self->pf_sections = load_stream(fp);
++
++    if (fp != stdin)
++        fclose(fp);
++
++    return problem_formatter_validate(self);
++}
++
++// generates report
++int
++problem_formatter_generate_report(const problem_formatter_t *self, problem_data_t *data, problem_report_t **report)
++{
++    problem_report_t *pr = problem_report_new();
++
++    for (GList *iter = self->pf_extra_sections; iter; iter = g_list_next(iter))
++        problem_report_add_custom_section(pr, ((struct extra_section *)iter->data)->pfes_name);
++
++    bool has_summary = false;
++    for (GList *iter = self->pf_sections; iter; iter = g_list_next(iter))
++    {
++        section_t *section = (section_t *)iter->data;
++
++        /* %summary is something special */
++        if (strcmp(section->name, "%summary") == 0)
++        {
++            has_summary = true;
++            format_percented_string((const char *)section->items->data, data,
++                                    problem_report_get_buffer(pr, PR_SEC_SUMMARY));
++        }
++        /* %attach as well */
++        else if (strcmp(section->name, "%attach") == 0)
++        {
++            problem_report_set_attachments(pr, get_attached_files(data, section->items, self->pf_sections));
++        }
++        else /* %description or a custom section (e.g. %additional_info) */
++        {
++            FILE *buffer = problem_report_get_buffer(pr, section->name + 1);
++
++            if (buffer != NULL)
++            {
++                log_debug("Formatting section : '%s'", section->name);
++                format_section(section, data, self->pf_sections, buffer);
++            }
++            else
++                log_warning("Unsupported section '%s'", section->name);
++        }
++    }
++
++    if (!has_summary) {
++        log_debug("Problem format misses section '%%summary'. Using the default one : '%s'.",
++                    self->pf_default_summary);
++
++        format_percented_string(self->pf_default_summary,
++                   data, problem_report_get_buffer(pr, PR_SEC_SUMMARY));
++    }
++
++    *report = pr;
++    return 0;
++}
+diff --git a/tests/Makefile.am b/tests/Makefile.am
+index a680f05..f6c5fee 100644
+--- a/tests/Makefile.am
++++ b/tests/Makefile.am
+@@ -42,7 +42,8 @@ TESTSUITE_AT = \
+   report_python.at \
+   xfuncs.at \
+   string_list.at \
+-  ureport.at
++  ureport.at \
++  problem_report.at
+ 
+ EXTRA_DIST += $(TESTSUITE_AT)
+ TESTSUITE = $(srcdir)/testsuite
+diff --git a/tests/problem_report.at b/tests/problem_report.at
+new file mode 100644
+index 0000000..8bc469f
+--- /dev/null
++++ b/tests/problem_report.at
+@@ -0,0 +1,690 @@
++# -*- Autotest -*-
++
++AT_BANNER([problem report])
++
++## ------- ##
++## summary ##
++## ------- ##
++
++AT_TESTFUN([summary],
++[[
++#include "problem_report.h"
++#include "internal_libreport.h"
++#include <assert.h>
++
++int main(int argc, char **argv)
++{
++    const char *const test_format_result[][2] = {
++        {
++            "%summary:: [abrt] trivial string",
++            "[abrt] trivial string"
++        },
++
++        {
++            "%summary:: [abrt] %package%",
++            "[abrt] libreport"
++        },
++
++        {
++            "%summary:: [abrt] %package%[[ : %crash_function%()]][[ : %does_not_exist%]][[ : %reason%]][[ TAINTED: %taint_flags%]]",
++            "[abrt] libreport : run_event() : Killed by SIGSEGV"
++        },
++
++        {
++            "%summary:: [abrt] %package%[[ : %crash_function%[[ : %does_not_exist%]]()]][[ : %reason%]]",
++            "[abrt] libreport : run_event() : Killed by SIGSEGV"
++        },
++
++        {
++            "%summary:: [abrt] %package%[[ : %does_not_exist%[[ : %crash_function%()]]]][[ : %reason%]]",
++            "[abrt] libreport : Killed by SIGSEGV"
++        },
++
++        {
++            "%summary:: [[%does_not_exist%]][[%once_more%]][abrt] %package%",
++            "[abrt] libreport"
++        },
++
++        {
++            "",
++            "Killed by SIGSEGV"
++        },
++    };
++
++    g_verbose = 3;
++
++    problem_data_t *data = problem_data_new();
++    problem_data_add_text_noteditable(data, "package", "libreport");
++    problem_data_add_text_noteditable(data, "crash_function", "run_event");
++    problem_data_add_text_noteditable(data, "reason", "Killed by SIGSEGV");
++
++    for (size_t i = 0; i < sizeof(test_format_result)/sizeof(*test_format_result); ++i) {
++        problem_formatter_t *pf = problem_formatter_new();
++        assert(!problem_formatter_load_string(pf, test_format_result[i][0]));
++
++        problem_report_t *pr = NULL;
++        assert(!problem_formatter_generate_report(pf, data, &pr));
++        assert(pr != NULL);
++
++        const char *summary = problem_report_get_summary(pr);
++        assert(summary != NULL);
++
++        fprintf(stderr, "expected: '%s'\n", test_format_result[i][1]);
++        fprintf(stderr, "result  : '%s'\n", summary);
++
++        assert(strcmp(test_format_result[i][1], summary) == 0);
++
++        problem_report_free(pr);
++        problem_formatter_free(pf);
++    }
++
++    problem_data_free(data);
++
++    return 0;
++}
++]])
++
++## ---------- ##
++## desciption ##
++## ---------- ##
++
++AT_TESTFUN([description],
++[[
++#include "problem_report.h"
++#include "internal_libreport.h"
++#include <assert.h>
++
++int main(int argc, char **argv)
++{
++    const char *const test_format_result[][2] = {
++        {
++            "\n"\
++            "\n"\
++            "\n"\
++            "Single line\n"
++            "\n"\
++            "\n"\
++            "\n",
++
++            "Single line\n"
++        },
++
++        {
++            "\n"\
++            "\n"\
++            "\n"\
++            "Comment:: %bare_comment"
++            "\n"\
++            "\n"\
++            "\n"\
++            "Ad-hoc line\n"
++            "\n"\
++            "\n"\
++            "\n"\
++            "Additional:: package,\\\n"
++            "uuid,cwd\\\\"
++            ",user_name"
++            "\n"\
++            "\n",
++
++            "Comment:\n" \
++            "Hello, world!\n"
++            "\n"\
++            "\n"\
++            "Ad-hoc line\n"
++            "\n"\
++            "\n"\
++            "\n"\
++            "Additional:\n"\
++            "package:        libreport\n"\
++            "uuid:           123456789ABCDEF\n"\
++            "user_name:      abrt\n",
++        },
++
++        {
++            ":: %bare_description",
++
++            "I run will_segfault and\n"\
++            "it crashed as expected\n"
++        },
++
++        {
++            "User:: %bare_user_name,uid\n"\
++            "Additional info:: -uuid,%oneline,-comment,-package",
++
++            "User:\n"\
++            "abrt\n"\
++            "uid:            69\n"\
++            "Additional info:\n"\
++            "analyzer:       CCpp\n"\
++            "root:           /var/run/mock/abrt\n"\
++            "type:           CCpp\n"
++        },
++
++        {
++            ":: %reporter",
++            NULL /* do no check results*/
++        },
++
++        {
++            "Truncated backtrace:: %bare_%short_backtrace",
++
++            "Truncated backtrace:\n"
++            "Thread no. 0 (7 frames)\n"
++            " #1 crash at will_segfault.c:19\n"
++            " #2 varargs at will_segfault.c:31\n"
++            " #3 inlined at will_segfault.c:40\n"
++            " #4 f at will_segfault.c:45\n"
++            " #5 callback at will_segfault.c:50\n"
++            " #6 call_me_back at libwillcrash.c:8\n"
++            " #7 recursive at will_segfault.c:59\n"
++        },
++    };
++
++    g_verbose = 3;
++
++    problem_data_t *data = problem_data_new();
++    problem_data_add_text_noteditable(data, "package", "libreport");
++    problem_data_add_text_noteditable(data, "analyzer", "CCpp");
++    problem_data_add_text_noteditable(data, "type", "CCpp");
++    problem_data_add_text_noteditable(data, "comment", "Hello, world!");
++    problem_data_add_text_noteditable(data, "uuid", "123456789ABCDEF");
++    problem_data_add_text_noteditable(data, "uid", "69");
++    problem_data_add_text_noteditable(data, "user_name", "abrt");
++    problem_data_add_text_noteditable(data, "root", "/var/run/mock/abrt");
++    problem_data_add_text_noteditable(data, "description", "I run will_segfault and\nit crashed as expected\n");
++    problem_data_add_text_noteditable(data, "backtrace",
++"Thread 1 (LWP 11865):\n"\
++"#0  printf (__fmt=0x400acf \"Result: %d\\n\") at /usr/include/bits/stdio2.h:104\n"\
++"No locals.\n"\
++"#1  crash (p=p@entry=0x0) at will_segfault.c:19\n"\
++"i = <error reading variable i (Cannot access memory at address 0x0)>\n"\
++"#2  0x0000000000400964 in varargs (num_args=1, num_args@entry=2) at will_segfault.c:31\n"\
++"p = <optimized out>\n"\
++"ap = {{gp_offset = 24, fp_offset = 32767, overflow_arg_area = 0x7fff4fc8a0c0, reg_save_area = 0x7fff4fc8a080}}\n"\
++"#3  0x00000000004009be in inlined (p=0x0) at will_segfault.c:40\n"\
++"num = 42\n"\
++"#4  f (p=p@entry=0x0) at will_segfault.c:45\n"\
++"No locals.\n"\
++"#5  0x00000000004009e9 in callback (data=data@entry=0x0) at will_segfault.c:50\n"\
++"No locals.\n"\
++"#6  0x00000032f76006f9 in call_me_back (cb=cb@entry=0x4009e0 <callback>, data=data@entry=0x0) at libwillcrash.c:8\n"\
++"res = <optimized out>\n"\
++"#7  0x0000000000400a14 in recursive (i=i@entry=0) at will_segfault.c:59\n"\
++"p = <optimized out>\n"\
++"#8  0x0000000000400a00 in recursive (i=i@entry=1) at will_segfault.c:66\n"\
++"No locals.\n"\
++"#9  0x0000000000400a00 in recursive (i=i@entry=2) at will_segfault.c:66\n"\
++"No locals.\n"\
++"#10 0x0000000000400775 in main (argc=<optimized out>, argv=<optimized out>) at will_segfault.c:83\n"\
++"No locals.\n"\
++"From                To                  Syms Read   Shared Object Library\n"\
++"0x00000032f76005f0  0x00000032f7600712  Yes         /lib64/libwillcrash.so.0\n"\
++"0x0000003245c1f4f0  0x0000003245d6aca4  Yes         /lib64/libc.so.6\n"\
++"0x0000003245800b10  0x000000324581b6d0  Yes         /lib64/ld-linux-x86-64.so.2\n"\
++"$1 = 0x0\n"
++"No symbol \"__glib_assert_msg\" in current context.\n"\
++"rax            0xf      15\n"\
++"rbx            0x0      0\n"\
++"rcx            0x7ff96f752000   140709293531136\n"\
++"rdx            0x3245fb9a40     215922481728\n"\
++"rsi            0x7ff96f752000   140709293531136\n"\
++"rdi            0x1      1\n"\
++"rbp            0x400a30 0x400a30 <__libc_csu_init>\n"\
++"rsp            0x7fff4fc8a050   0x7fff4fc8a050\n"\
++"r8             0xffffffff       4294967295\n"\
++"r9             0x0      0\n"\
++"r10            0x22     34\n"\
++"r11            0x246    582\n"\
++"r12            0x40079f 4196255\n"\
++"r13            0x7fff4fc8a210   140734531936784\n"\
++"r14            0x0      0\n"\
++"r15            0x0      0\n"\
++"rip            0x4008ae 0x4008ae <crash+14>\n"\
++"eflags         0x10246  [ PF ZF IF RF ]\n"\
++"cs             0x33     51\n"\
++"ss             0x2b     43\n"\
++"ds             0x0      0\n"\
++"es             0x0      0\n"\
++"fs             0x0      0\n"\
++"gs             0x0      0\n"\
++"st0            0        (raw 0x00000000000000000000)\n"\
++"st1            0        (raw 0x00000000000000000000)\n"\
++"st2            0        (raw 0x00000000000000000000)\n"\
++"st3            0        (raw 0x00000000000000000000)\n"\
++"st4            0        (raw 0x00000000000000000000)\n"\
++"st5            0        (raw 0x00000000000000000000)\n"\
++"st6            0        (raw 0x00000000000000000000)\n"\
++"st7            0        (raw 0x00000000000000000000)\n"\
++"fctrl          0x37f    895\n"\
++"fstat          0x0      0\n"\
++"ftag           0xffff   65535\n"\
++"fiseg          0x0      0\n"\
++"fioff          0x0      0\n"\
++"foseg          0x0      0\n"\
++"fooff          0x0      0\n"\
++"fop            0x0      0\n"\
++"xmm0           {v4_float = {0x0, 0x0, 0x0, 0x0}, v2_double = {0x0, 0x0}, v16_int8 = {0x0 <repeats 16 times>}, v8_int16 = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, v4_int32 = {0x0, 0x0, 0x0, 0x0}, v2_int64 = {0x0, 0x0}, uint128 = 0x00000000000000000000000000000000}\n"\
++"xmm1           {v4_float = {0x0, 0x0, 0x0, 0x0}, v2_double = {0x0, 0x0}, v16_int8 = {0x2f <repeats 16 times>}, v8_int16 = {0x2f2f, 0x2f2f, 0x2f2f, 0x2f2f, 0x2f2f, 0x2f2f, 0x2f2f, 0x2f2f}, v4_int32 = {0x2f2f2f2f, 0x2f2f2f2f, 0x2f2f2f2f, 0x2f2f2f2f}, v2_int64 = {0x2f2f2f2f2f2f2f2f, 0x2f2f2f2f2f2f2f2f}, uint128 = 0x2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f}\n"\
++"xmm2           {v4_float = {0x0, 0x0, 0x0, 0x0}, v2_double = {0x0, 0x0}, v16_int8 = {0x0 <repeats 16 times>}, v8_int16 = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, v4_int32 = {0x0, 0x0, 0x0, 0x0}, v2_int64 = {0x0, 0x0}, uint128 = 0x00000000000000000000000000000000}\n"\
++"xmm3           {v4_float = {0x0, 0x0, 0x0, 0x0}, v2_double = {0x0, 0x0}, v16_int8 = {0x0 <repeats 13 times>, 0xff, 0x0, 0x0}, v8_int16 = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xff00, 0x0}, v4_int32 = {0x0, 0x0, 0x0, 0xff00}, v2_int64 = {0x0, 0xff0000000000}, uint128 = 0x0000ff00000000000000000000000000}\n"\
++"xmm4           {v4_float = {0x0, 0x0, 0x0, 0x0}, v2_double = {0x0, 0x0}, v16_int8 = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xff, 0x0, 0x0, 0x0, 0x0, 0x0}, v8_int16 = {0x0, 0x0, 0x0, 0x0, 0x0, 0xff, 0x0, 0x0}, v4_int32 = {0x0, 0x0, 0xff0000, 0x0}, v2_int64 = {0x0, 0xff0000}, uint128 = 0x0000000000ff00000000000000000000}\n"\
++"xmm5           {v4_float = {0x0, 0x0, 0x0, 0x0}, v2_double = {0x0, 0x0}, v16_int8 = {0x0 <repeats 16 times>}, v8_int16 = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, v4_int32 = {0x0, 0x0, 0x0, 0x0}, v2_int64 = {0x0, 0x0}, uint128 = 0x00000000000000000000000000000000}\n"\
++"xmm6           {v4_float = {0x0, 0x0, 0x0, 0x0}, v2_double = {0x0, 0x0}, v16_int8 = {0x0 <repeats 16 times>}, v8_int16 = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, v4_int32 = {0x0, 0x0, 0x0, 0x0}, v2_int64 = {0x0, 0x0}, uint128 = 0x00000000000000000000000000000000}\n"\
++"xmm7           {v4_float = {0x0, 0x0, 0x0, 0x0}, v2_double = {0x0, 0x0}, v16_int8 = {0x0 <repeats 16 times>}, v8_int16 = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, v4_int32 = {0x0, 0x0, 0x0, 0x0}, v2_int64 = {0x0, 0x0}, uint128 = 0x00000000000000000000000000000000}\n"\
++"xmm8           {v4_float = {0x0, 0x0, 0x0, 0x0}, v2_double = {0x0, 0x0}, v16_int8 = {0x0 <repeats 16 times>}, v8_int16 = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, v4_int32 = {0x0, 0x0, 0x0, 0x0}, v2_int64 = {0x0, 0x0}, uint128 = 0x00000000000000000000000000000000}\n"\
++"xmm9           {v4_float = {0x0, 0x0, 0x0, 0x0}, v2_double = {0x0, 0x0}, v16_int8 = {0x0 <repeats 16 times>}, v8_int16 = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, v4_int32 = {0x0, 0x0, 0x0, 0x0}, v2_int64 = {0x0, 0x0}, uint128 = 0x00000000000000000000000000000000}\n"\
++"xmm10          {v4_float = {0x0, 0x0, 0x0, 0x0}, v2_double = {0x0, 0x0}, v16_int8 = {0x0 <repeats 16 times>}, v8_int16 = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, v4_int32 = {0x0, 0x0, 0x0, 0x0}, v2_int64 = {0x0, 0x0}, uint128 = 0x00000000000000000000000000000000}\n"\
++"xmm11          {v4_float = {0x0, 0x0, 0x0, 0x0}, v2_double = {0x0, 0x0}, v16_int8 = {0x0 <repeats 16 times>}, v8_int16 = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, v4_int32 = {0x0, 0x0, 0x0, 0x0}, v2_int64 = {0x0, 0x0}, uint128 = 0x00000000000000000000000000000000}\n"\
++"xmm12          {v4_float = {0x0, 0x0, 0x0, 0x0}, v2_double = {0x0, 0x0}, v16_int8 = {0x0 <repeats 14 times>, 0xff, 0x0}, v8_int16 = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xff}, v4_int32 = {0x0, 0x0, 0x0, 0xff0000}, v2_int64 = {0x0, 0xff000000000000}, uint128 = 0x00ff0000000000000000000000000000}\n"\
++"xmm13          {v4_float = {0x0, 0x0, 0x0, 0x0}, v2_double = {0x0, 0x0}, v16_int8 = {0x0 <repeats 16 times>}, v8_int16 = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, v4_int32 = {0x0, 0x0, 0x0, 0x0}, v2_int64 = {0x0, 0x0}, uint128 = 0x00000000000000000000000000000000}\n"\
++"xmm14          {v4_float = {0x0, 0x0, 0x0, 0x0}, v2_double = {0x0, 0x0}, v16_int8 = {0x0 <repeats 16 times>}, v8_int16 = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, v4_int32 = {0x0, 0x0, 0x0, 0x0}, v2_int64 = {0x0, 0x0}, uint128 = 0x00000000000000000000000000000000}\n"\
++"xmm15          {v4_float = {0x0, 0x0, 0x0, 0x0}, v2_double = {0x0, 0x0}, v16_int8 = {0x0 <repeats 16 times>}, v8_int16 = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, v4_int32 = {0x0, 0x0, 0x0, 0x0}, v2_int64 = {0x0, 0x0}, uint128 = 0x00000000000000000000000000000000}\n"\
++"mxcsr          0x1f80   [ IM DM ZM OM UM PM ]\n"\
++"Dump of assembler code for function crash:\n"\
++"   0x00000000004008a0 <+0>:     push   %rbx\n"\
++"   0x00000000004008a1 <+1>:     mov    %rdi,%rbx\n"\
++"   0x00000000004008a4 <+4>:     mov    $0x400ac0,%edi\n"\
++"   0x00000000004008a9 <+9>:     callq  0x4006f0 <puts@plt>\n"\
++"   => 0x00000000004008ae <+14>:    mov    (%rbx),%edx\n"\
++"   0x00000000004008b0 <+16>:    mov    $0x400acf,%esi\n"\
++"   0x00000000004008b5 <+21>:    mov    $0x1,%edi\n"\
++"   0x00000000004008ba <+26>:    xor    %eax,%eax\n"\
++"   0x00000000004008bc <+28>:    callq  0x400740 <__printf_chk@plt>\n"\
++"   0x00000000004008c1 <+33>:    pop    %rbx\n"\
++"   0x00000000004008c2 <+34>:    retq\n"\
++"End of assembler dump.\n"
++            );
++
++    for (size_t i = 0; i < sizeof(test_format_result)/sizeof(*test_format_result); ++i) {
++        problem_formatter_t *pf = problem_formatter_new();
++        assert(!problem_formatter_load_string(pf, test_format_result[i][0]));
++
++        problem_report_t *pr = NULL;
++        assert(!problem_formatter_generate_report(pf, data, &pr));
++        assert(pr != NULL);
++
++        const char *summary = problem_report_get_description(pr);
++        assert(summary != NULL);
++
++        if (test_format_result[i][1] != NULL) {
++            fprintf(stderr, "####\n");
++            fprintf(stderr, "expected: '%s'\n", test_format_result[i][1]);
++            fprintf(stderr, "====\n");
++            fprintf(stderr, "result  : '%s'\n", summary);
++            fprintf(stderr, "####\n");
++
++            assert(strcmp(test_format_result[i][1], summary) == 0);
++        }
++
++        problem_report_free(pr);
++        problem_formatter_free(pf);
++    }
++
++    problem_data_free(data);
++
++    return 0;
++}
++]])
++
++## ------ ##
++## attach ##
++## ------ ##
++
++AT_TESTFUN([attach],
++[[
++#include "problem_report.h"
++#include "internal_libreport.h"
++#include <assert.h>
++
++int main(int argc, char **argv)
++{
++    g_verbose = 3;
++
++    const char *fst[] = { "backtrace", "screenshot", "description", NULL };
++
++    struct test_case {
++        const char *format;
++        const char *const *files;
++    } cases [] = {
++        {
++            .format = "%attach:: %multiline,%binary,-coredump",
++            .files = fst,
++        }
++    };
++
++    problem_data_t *data = problem_data_new();
++    problem_data_add_text_noteditable(data, "package", "libreport");
++    problem_data_add_text_noteditable(data, "analyzer", "CCpp");
++    problem_data_add_text_noteditable(data, "type", "CCpp");
++    problem_data_add_text_noteditable(data, "comment", "Hello, world!");
++    problem_data_add_text_noteditable(data, "uuid", "123456789ABCDEF");
++    problem_data_add_text_noteditable(data, "uid", "69");
++    problem_data_add_text_noteditable(data, "user_name", "abrt");
++    problem_data_add_text_noteditable(data, "root", "/var/run/mock/abrt");
++    problem_data_add_text_noteditable(data, "description", "I run will_segfault and\nit crashed as expected\n");
++    problem_data_add_file(data, "coredump", "/what/ever/path/to/coredump");
++    problem_data_add_file(data, "screenshot", "/what/ever/path/to/screenshot");
++    problem_data_add_text_noteditable(data, "backtrace",
++"Thread 1 (LWP 11865):\n"\
++"#0  printf (__fmt=0x400acf \"Result: %d\\n\") at /usr/include/bits/stdio2.h:104\n"\
++"No locals.\n"\
++"#1  crash (p=p@entry=0x0) at will_segfault.c:19\n"\
++"i = <error reading variable i (Cannot access memory at address 0x0)>\n"\
++"#2  0x0000000000400964 in varargs (num_args=1, num_args@entry=2) at will_segfault.c:31\n"\
++"p = <optimized out>\n"\
++"ap = {{gp_offset = 24, fp_offset = 32767, overflow_arg_area = 0x7fff4fc8a0c0, reg_save_area = 0x7fff4fc8a080}}\n"\
++"#3  0x00000000004009be in inlined (p=0x0) at will_segfault.c:40\n"\
++"num = 42\n"\
++"#4  f (p=p@entry=0x0) at will_segfault.c:45\n"\
++"No locals.\n"\
++"#5  0x00000000004009e9 in callback (data=data@entry=0x0) at will_segfault.c:50\n"\
++"No locals.\n"\
++"#6  0x00000032f76006f9 in call_me_back (cb=cb@entry=0x4009e0 <callback>, data=data@entry=0x0) at libwillcrash.c:8\n"\
++"res = <optimized out>\n"\
++"#7  0x0000000000400a14 in recursive (i=i@entry=0) at will_segfault.c:59\n"\
++"p = <optimized out>\n"\
++"#8  0x0000000000400a00 in recursive (i=i@entry=1) at will_segfault.c:66\n"\
++"No locals.\n"\
++"#9  0x0000000000400a00 in recursive (i=i@entry=2) at will_segfault.c:66\n"\
++"No locals.\n"\
++"#10 0x0000000000400775 in main (argc=<optimized out>, argv=<optimized out>) at will_segfault.c:83\n"\
++"No locals.\n");
++
++    for (size_t i = 0; i < sizeof(cases)/sizeof(*cases); ++i) {
++        fprintf(stderr, "%s", cases[i].format);
++
++        problem_formatter_t *pf = problem_formatter_new();
++        assert(!problem_formatter_load_string(pf, cases[i].format));
++
++        problem_report_t *pr = NULL;
++        assert(!problem_formatter_generate_report(pf, data, &pr));
++        assert(pr != NULL);
++
++        const char *const *iter = cases[i].files;
++
++        if (*iter != NULL) {
++            assert(problem_report_get_attachments(pr));
++        }
++
++        GList *clone = g_list_copy_deep(problem_report_get_attachments(pr), (GCopyFunc)xstrdup, NULL);
++
++        while (*iter) {
++            GList *item = g_list_find_custom(clone, *iter, (GCompareFunc)strcmp);
++
++            if (item == NULL) {
++                fprintf(stderr, "format:       '%s'\n", cases[i].format);
++                fprintf(stderr, "missing file: '%s'\n", *iter);
++                abort();
++            }
++            else {
++                free(item->data);
++                clone = g_list_delete_link(clone, item);
++            }
++
++            ++iter;
++        }
++
++        if (clone != NULL) {
++            for (GList *iter = clone; iter; iter = g_list_next(iter)) {
++                fprintf(stderr, "extra : '%s'\n", (const char *)iter->data);
++            }
++            abort();
++        }
++
++        problem_report_free(pr);
++        problem_formatter_free(pf);
++    }
++
++    problem_data_free(data);
++
++    return 0;
++}
++]])
++
++
++## -------------- ##
++## custom_section ##
++## -------------- ##
++
++AT_TESTFUN([custom_section],
++[[
++#include "problem_report.h"
++#include "internal_libreport.h"
++#include <assert.h>
++
++int main(int argc, char **argv)
++{
++    g_verbose = 3;
++
++    struct test_case;
++    struct test_case {
++        const char *section_name;
++        int section_flags;
++        int retval_load;
++        int retval_generate;
++        const char *format;
++        const char *output;
++    } cases [] = {
++        {   .section_name = NULL,
++            .section_flags = 0,
++            .retval_load = 1,
++            .retval_generate = 0,
++            .format =
++                "%additional_info::\n"
++                ":: package,\\\n"
++                "uuid,cwd\\\\"
++                ",user_name"
++                ,
++            .output = NULL,
++        },
++
++        {   .section_name = "additional_info",
++            .section_flags = 0,
++            .retval_load = 0,
++            .retval_generate = 0,
++            .format =
++                "%additional_info::\n"
++                ":: package,\\\n"
++                "uuid,cwd\\\\"
++                ",user_name"
++                ,
++            .output =
++                "package:        libreport\n"
++                "uuid:           0123456789ABCDEF\n"
++                "user_name:      abrt\n"
++                ,
++        },
++
++        {   .section_name = "additional_info",
++            .section_flags = PFFF_REQUIRED,
++            .retval_load = 1,
++            .retval_generate = 0,
++            .format = "%summary:: [abrt] %package%\n:: %reporter\n",
++            .output = NULL,
++        },
++
++        {   .section_name = "additional_info",
++            .section_flags = 0,
++            .retval_load = 0,
++            .retval_generate = 0,
++            .format = "%summary:: [abrt] %package%\n:: %reporter\n",
++            .output = "",
++        },
++
++        {   .section_name = "additional_info",
++            .section_flags = 0,
++            .retval_load = 0,
++            .retval_generate = 0,
++            .format =
++                "%additional_info:: root\n"
++                "Info:: package,\\\n"
++                "uuid,cwd\\\\"
++                ",user_name"
++                ,
++            .output =
++                "Info:\n"
++                "package:        libreport\n"
++                "uuid:           0123456789ABCDEF\n"
++                "user_name:      abrt\n"
++                ,
++        },
++    };
++
++    problem_data_t *data = problem_data_new();
++    problem_data_add_text_noteditable(data, "package", "libreport");
++    problem_data_add_text_noteditable(data, "crash_function", "run_event");
++    problem_data_add_text_noteditable(data, "reason", "Killed by SIGSEGV");
++    problem_data_add_text_noteditable(data, "uuid", "0123456789ABCDEF");
++    problem_data_add_text_noteditable(data, "user_name", "abrt");
++
++    for (size_t i = 0; i < sizeof(cases)/sizeof(*cases); ++i) {
++        fprintf(stderr, "#### Test case %zd ####\n", i + 1);
++
++        struct test_case *tcase = cases + i;
++        problem_formatter_t *pf = problem_formatter_new();
++
++        if (tcase->section_name != NULL) {
++            problem_formatter_add_section(pf, tcase->section_name, tcase->section_flags);
++        }
++
++        int r = problem_formatter_load_string(pf, tcase->format);
++        if (r != tcase->retval_load) {
++            fprintf(stderr, "Load : expected : %d , got : %d\n", tcase->retval_load, r);
++            abort();
++        }
++
++        if (tcase->retval_load != 0) {
++            goto next_test_case;
++        }
++
++        problem_report_t *pr = NULL;
++        r = problem_formatter_generate_report(pf, data, &pr);
++        if (r != tcase->retval_generate) {
++            fprintf(stderr, "Generaet : expected : %d , got : %d\n", tcase->retval_generate, r);
++            abort();
++        }
++
++        if (tcase->retval_generate != 0) {
++            goto next_test_case;
++        }
++
++        const char *output = problem_report_get_section(pr, tcase->section_name);
++        assert(output);
++
++        if (strcmp(output, tcase->output) != 0) {
++            fprintf(stderr, "expected:\n'%s'\n", tcase->output);
++            fprintf(stderr, "result  :\n'%s'\n", output);
++            abort();
++        }
++
++        problem_report_free(pr);
++
++next_test_case:
++        problem_formatter_free(pf);
++        fprintf(stderr, "#### Finished - Test case %zd ####\n", i + 1);
++    }
++
++    problem_data_free(data);
++
++    return 0;
++}
++]])
++
++## ------ ##
++## sanity ##
++## ------ ##
++
++AT_TESTFUN([sanity],
++[[
++#include "problem_report.h"
++#include "internal_libreport.h"
++#include <errno.h>
++#include <assert.h>
++
++void assert_equal_strings(const char *res, const char *exp)
++{
++    if (    (res == NULL && exp != NULL)
++        ||  (res != NULL && exp == NULL)
++        || ((res != NULL && exp != NULL) && strcmp(res, exp) != 0)
++        ) {
++        fprintf(stderr, "expected : '%s'\n", exp);
++        fprintf(stderr, "result   : '%s'\n", res);
++        abort();
++    }
++}
++
++int main(int argc, char **argv)
++{
++    g_verbose = 3;
++
++    problem_formatter_t *pf = problem_formatter_new();
++
++    assert(problem_formatter_add_section(pf, "summary", 0) == -EEXIST);
++    assert(problem_formatter_add_section(pf, "description", 0) == -EEXIST);
++    assert(problem_formatter_add_section(pf, "attach", 0) == -EEXIST);
++
++    assert(problem_formatter_add_section(pf, "additional_info", 0) == 0);
++    assert(problem_formatter_add_section(pf, "additional_info", 0) == -EEXIST);
++
++    problem_data_t *data = problem_data_new();
++    problem_data_add_text_noteditable(data, "package", "libreport");
++    problem_data_add_text_noteditable(data, "crash_function", "run_event");
++    problem_data_add_text_noteditable(data, "reason", "Killed by SIGSEGV");
++    problem_data_add_text_noteditable(data, "comment", "Hello, world!");
++    problem_data_add_text_noteditable(data, "root", "/var/run/mock/abrt");
++    problem_data_add_text_noteditable(data, "user_name", "abrt");
++    problem_data_add_file(data, "screenshot", "/what/ever/path/to/screenshot");
++
++    problem_formatter_load_string(pf,
++            "%summary:: [abrt] %package% : %reason%\n\n"
++            "Description:: %bare_comment\n\n"
++            "%attach:: screenshot\n\n"
++            "%additional_info::\n\n"
++            "User:: root,user_name,uid\n\n\n"
++            );
++
++    problem_report_t *pr = NULL;
++    problem_formatter_generate_report(pf, data, &pr);
++
++    assert_equal_strings(problem_report_get_summary(pr),
++                         "[abrt] libreport : Killed by SIGSEGV");
++
++    problem_report_buffer_printf(problem_report_get_buffer(pr, PR_SEC_SUMMARY), " - test");
++
++    assert_equal_strings(problem_report_get_summary(pr),
++                         "[abrt] libreport : Killed by SIGSEGV - test");
++
++
++    assert_equal_strings(problem_report_get_description(pr),
++            "Description:\nHello, world!\n");
++
++    problem_report_buffer_printf(problem_report_get_buffer(pr, PR_SEC_DESCRIPTION), "Test line\n");
++
++    assert_equal_strings(problem_report_get_description(pr),
++            "Description:\nHello, world!\nTest line\n");
++
++
++    assert_equal_strings(problem_report_get_section(pr, "additional_info"),
++            "User:\n"
++            "root:           /var/run/mock/abrt\n"
++            "user_name:      abrt\n"
++            );
++
++    problem_report_buffer_printf(problem_report_get_buffer(pr, "additional_info"), "uid:            42\n");
++
++    assert_equal_strings(problem_report_get_section(pr, "additional_info"),
++            "User:\n"
++            "root:           /var/run/mock/abrt\n"
++            "user_name:      abrt\n"
++            "uid:            42\n"
++            );
++
++
++    problem_report_free(pr);
++    problem_data_free(data);
++    problem_formatter_free(pf);
++
++    return 0;
++}
++]])
+diff --git a/tests/testsuite.at b/tests/testsuite.at
+index abad32b..340c758 100644
+--- a/tests/testsuite.at
++++ b/tests/testsuite.at
+@@ -17,3 +17,4 @@ m4_include([xml_definition.at])
+ m4_include([report_python.at])
+ m4_include([string_list.at])
+ m4_include([ureport.at])
++m4_include([problem_report.at])
+-- 
+1.8.3.1
+
diff --git a/SOURCES/0128-bugzilla-port-to-Problem-Format-API.patch b/SOURCES/0128-bugzilla-port-to-Problem-Format-API.patch
new file mode 100644
index 0000000..e6f9bbe
--- /dev/null
+++ b/SOURCES/0128-bugzilla-port-to-Problem-Format-API.patch
@@ -0,0 +1,781 @@
+From 4dd20b271a2e0bf7497654cec97541898b3bae31 Mon Sep 17 00:00:00 2001
+From: Jakub Filak <jfilak@redhat.com>
+Date: Thu, 4 Dec 2014 08:45:07 +0100
+Subject: [PATCH 128/136] bugzilla: port to Problem Format API
+
+Related to #303
+
+Signed-off-by: Jakub Filak <jfilak@redhat.com>
+---
+ src/plugins/reporter-bugzilla.c | 691 ++++------------------------------------
+ 1 file changed, 59 insertions(+), 632 deletions(-)
+
+diff --git a/src/plugins/reporter-bugzilla.c b/src/plugins/reporter-bugzilla.c
+index 097924e..38f48ef 100644
+--- a/src/plugins/reporter-bugzilla.c
++++ b/src/plugins/reporter-bugzilla.c
+@@ -17,515 +17,11 @@
+     51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+ #include "internal_libreport.h"
++#include "problem_report.h"
+ #include "client.h"
+ #include "abrt_xmlrpc.h"
+ #include "rhbz.h"
+ 
+-#include <satyr/stacktrace.h>
+-#include <satyr/abrt.h>
+-
+-struct section_t {
+-    char *name;
+-    GList *items;
+-};
+-typedef struct section_t section_t;
+-
+-
+-/* Utility functions */
+-
+-static
+-GList* split_string_on_char(const char *str, char ch)
+-{
+-    GList *list = NULL;
+-    for (;;)
+-    {
+-        const char *delim = strchrnul(str, ch);
+-        list = g_list_prepend(list, xstrndup(str, delim - str));
+-        if (*delim == '\0')
+-            break;
+-        str = delim + 1;
+-    }
+-    return g_list_reverse(list);
+-}
+-
+-static
+-int compare_item_name(const char *lookup, const char *name)
+-{
+-    if (lookup[0] == '-')
+-        lookup++;
+-    else if (strncmp(lookup, "%bare_", 6) == 0)
+-        lookup += 6;
+-    return strcmp(lookup, name);
+-}
+-
+-static
+-int is_item_name_in_section(const section_t *lookup, const char *name)
+-{
+-    if (g_list_find_custom(lookup->items, name, (GCompareFunc)compare_item_name))
+-        return 0; /* "found it!" */
+-    return 1;
+-}
+-
+-static
+-bool is_explicit_or_forbidden(const char *name, GList *comment_fmt_spec)
+-{
+-    return g_list_find_custom(comment_fmt_spec, name, (GCompareFunc)is_item_name_in_section);
+-}
+-
+-static
+-GList* load_bzrep_conf_file(const char *path)
+-{
+-    FILE *fp = stdin;
+-    if (strcmp(path, "-") != 0)
+-    {
+-        fp = fopen(path, "r");
+-        if (!fp)
+-            return NULL;
+-    }
+-
+-    GList *sections = NULL;
+-
+-    char *line;
+-    while ((line = xmalloc_fgetline(fp)) != NULL)
+-    {
+-        /* Skip comments */
+-        char first = *skip_whitespace(line);
+-        if (first == '#')
+-            goto free_line;
+-
+-        /* Handle trailing backslash continuation */
+- check_continuation: ;
+-        unsigned len = strlen(line);
+-        if (len && line[len-1] == '\\')
+-        {
+-            line[len-1] = '\0';
+-            char *next_line = xmalloc_fgetline(fp);
+-            if (next_line)
+-            {
+-                line = append_to_malloced_string(line, next_line);
+-                free(next_line);
+-                goto check_continuation;
+-            }
+-        }
+-
+-        /* We are reusing line buffer to form temporary
+-         * "key\0values\0..." in its beginning
+-         */
+-        bool summary_line = false;
+-        char *value = NULL;
+-        char *src;
+-        char *dst;
+-        for (src = dst = line; *src; src++)
+-        {
+-            char c = *src;
+-            /* did we reach the value list? */
+-            if (!value && c == ':' && src[1] == ':')
+-            {
+-                *dst++ = '\0'; /* terminate key */
+-                src += 2;
+-                value = dst; /* remember where value starts */
+-                summary_line = (strcmp(line, "%summary") == 0);
+-                if (summary_line)
+-                {
+-                    value = src;
+-                    break;
+-                }
+-                continue;
+-            }
+-            /* skip whitespace in value list */
+-            if (value && isspace(c))
+-                continue;
+-            *dst++ = c; /* store next key or value char */
+-        }
+-
+-        GList *item_list = NULL;
+-        if (summary_line)
+-        {
+-            /* %summary is special */
+-            item_list = g_list_append(NULL, xstrdup(skip_whitespace(value)));
+-        }
+-        else
+-        {
+-            *dst = '\0'; /* terminate value (or key) */
+-            if (value)
+-                item_list = split_string_on_char(value, ',');
+-        }
+-
+-        section_t *sec = xzalloc(sizeof(*sec));
+-        sec->name = xstrdup(line);
+-        sec->items = item_list;
+-        sections = g_list_prepend(sections, sec);
+-
+- free_line:
+-        free(line);
+-    }
+-
+-    if (fp != stdin)
+-        fclose(fp);
+-
+-    return g_list_reverse(sections);
+-}
+-
+-
+-/* Summary generation */
+-
+-#define MAX_OPT_DEPTH 10
+-static
+-char *format_percented_string(const char *str, problem_data_t *pd)
+-{
+-    size_t old_pos[MAX_OPT_DEPTH] = { 0 };
+-    int okay[MAX_OPT_DEPTH] = { 1 };
+-    int opt_depth = 1;
+-    struct strbuf *result = strbuf_new();
+-
+-    while (*str) {
+-        switch (*str) {
+-        default:
+-            strbuf_append_char(result, *str);
+-            str++;
+-            break;
+-        case '\\':
+-            if (str[1])
+-                str++;
+-            strbuf_append_char(result, *str);
+-            str++;
+-            break;
+-        case '[':
+-            if (str[1] == '[' && opt_depth < MAX_OPT_DEPTH)
+-            {
+-                old_pos[opt_depth] = result->len;
+-                okay[opt_depth] = 1;
+-                opt_depth++;
+-                str += 2;
+-            } else {
+-                strbuf_append_char(result, *str);
+-                str++;
+-            }
+-            break;
+-        case ']':
+-            if (str[1] == ']' && opt_depth > 1)
+-            {
+-                opt_depth--;
+-                if (!okay[opt_depth])
+-                {
+-                    result->len = old_pos[opt_depth];
+-                    result->buf[result->len] = '\0';
+-                }
+-                str += 2;
+-            } else {
+-                strbuf_append_char(result, *str);
+-                str++;
+-            }
+-            break;
+-        case '%': ;
+-            char *nextpercent = strchr(++str, '%');
+-            if (!nextpercent)
+-            {
+-                error_msg_and_die("Unterminated %%element%%: '%s'", str - 1);
+-            }
+-
+-            *nextpercent = '\0';
+-            const problem_item *item = problem_data_get_item_or_NULL(pd, str);
+-            *nextpercent = '%';
+-
+-            if (item && (item->flags & CD_FLAG_TXT))
+-                strbuf_append_str(result, item->content);
+-            else
+-                okay[opt_depth - 1] = 0;
+-            str = nextpercent + 1;
+-            break;
+-        }
+-    }
+-
+-    if (opt_depth > 1)
+-    {
+-        error_msg_and_die("Unbalanced [[ ]] bracket");
+-    }
+-
+-    if (!okay[0])
+-    {
+-        error_msg("Undefined variable outside of [[ ]] bracket");
+-    }
+-
+-    return strbuf_free_nobuf(result);
+-}
+-
+-static
+-char *create_summary_string(problem_data_t *pd, GList *comment_fmt_spec)
+-{
+-    GList *l = comment_fmt_spec;
+-    while (l)
+-    {
+-        section_t *sec = l->data;
+-        l = l->next;
+-
+-        /* Find %summary" */
+-        if (strcmp(sec->name, "%summary") != 0)
+-            continue;
+-
+-        GList *item = sec->items;
+-        if (!item)
+-            /* not supposed to happen, there will be at least "" */
+-            error_msg_and_die("BUG in %%summary parser");
+-
+-        const char *str = item->data;
+-        return format_percented_string(str, pd);
+-    }
+-
+-    return format_percented_string("%reason%", pd);
+-}
+-
+-
+-/* BZ comment generation */
+-
+-static
+-int append_text(struct strbuf *result, const char *item_name, const char *content, bool print_item_name)
+-{
+-    char *eol = strchrnul(content, '\n');
+-    if (eol[0] == '\0' || eol[1] == '\0')
+-    {
+-        /* one-liner */
+-        int pad = 16 - (strlen(item_name) + 2);
+-        if (pad < 0)
+-            pad = 0;
+-        if (print_item_name)
+-            strbuf_append_strf(result,
+-                    eol[0] == '\0' ? "%s: %*s%s\n" : "%s: %*s%s",
+-                    item_name, pad, "", content
+-            );
+-        else
+-            strbuf_append_strf(result,
+-                    eol[0] == '\0' ? "%s\n" : "%s",
+-                    content
+-            );
+-    }
+-    else
+-    {
+-        /* multi-line item */
+-        if (print_item_name)
+-            strbuf_append_strf(result, "%s:\n", item_name);
+-        for (;;)
+-        {
+-            eol = strchrnul(content, '\n');
+-            strbuf_append_strf(result,
+-                    /* For %bare_multiline_item, we don't want to print colons */
+-                    (print_item_name ? ":%.*s\n" : "%.*s\n"),
+-                    (int)(eol - content), content
+-            );
+-            if (eol[0] == '\0' || eol[1] == '\0')
+-                break;
+-            content = eol + 1;
+-        }
+-    }
+-    return 1;
+-}
+-
+-static
+-int append_short_backtrace(struct strbuf *result, problem_data_t *problem_data, size_t max_text_size, bool print_item_name)
+-{
+-    const problem_item *item = problem_data_get_item_or_NULL(problem_data,
+-                                                             FILENAME_BACKTRACE);
+-    if (!item)
+-        return 0; /* "I did not print anything" */
+-    if (!(item->flags & CD_FLAG_TXT))
+-        return 0; /* "I did not print anything" */
+-
+-    char *truncated = NULL;
+-
+-    if (strlen(item->content) >= max_text_size)
+-    {
+-        char *error_msg = NULL;
+-        const char *analyzer = problem_data_get_content_or_NULL(problem_data, FILENAME_ANALYZER);
+-        if (!analyzer)
+-            return 0;
+-
+-        /* For CCpp crashes, use the GDB-produced backtrace which should be
+-         * available by now. sr_abrt_type_from_analyzer returns SR_REPORT_CORE
+-         * by default for CCpp crashes.
+-         */
+-        enum sr_report_type report_type = sr_abrt_type_from_analyzer(analyzer);
+-        if (strcmp(analyzer, "CCpp") == 0)
+-            report_type = SR_REPORT_GDB;
+-
+-        struct sr_stacktrace *backtrace = sr_stacktrace_parse(report_type,
+-                item->content, &error_msg);
+-
+-        if (!backtrace)
+-        {
+-            log(_("Can't parse backtrace: %s"), error_msg);
+-            free(error_msg);
+-            return 0;
+-        }
+-
+-        /* Get optimized thread stack trace for 10 top most frames */
+-        truncated = sr_stacktrace_to_short_text(backtrace, 10);
+-        sr_stacktrace_free(backtrace);
+-
+-        if (!truncated)
+-        {
+-            log(_("Can't generate stacktrace description (no crash thread?)"));
+-            return 0;
+-        }
+-    }
+-
+-    append_text(result,
+-                /*item_name:*/ truncated ? "truncated_backtrace" : FILENAME_BACKTRACE,
+-                /*content:*/   truncated ? truncated             : item->content,
+-                print_item_name
+-    );
+-    free(truncated);
+-    return 1;
+-}
+-
+-static
+-int append_item(struct strbuf *result, const char *item_name, problem_data_t *pd, GList *comment_fmt_spec)
+-{
+-    bool print_item_name = (strncmp(item_name, "%bare_", strlen("%bare_")) != 0);
+-    if (!print_item_name)
+-        item_name += strlen("%bare_");
+-
+-    if (item_name[0] != '%')
+-    {
+-        struct problem_item *item = problem_data_get_item_or_NULL(pd, item_name);
+-        if (!item)
+-            return 0; /* "I did not print anything" */
+-        if (!(item->flags & CD_FLAG_TXT))
+-            return 0; /* "I did not print anything" */
+-
+-        char *formatted = problem_item_format(item);
+-        char *content = formatted ? formatted : item->content;
+-        append_text(result, item_name, content, print_item_name);
+-        free(formatted);
+-        return 1; /* "I printed something" */
+-    }
+-
+-    /* Special item name */
+-
+-    /* Compat with previously-existed ad-hockery: %short_backtrace */
+-    if (strcmp(item_name, "%short_backtrace") == 0)
+-        return append_short_backtrace(result, pd, CD_TEXT_ATT_SIZE_BZ, print_item_name);
+-
+-    /* Compat with previously-existed ad-hockery: %reporter */
+-    if (strcmp(item_name, "%reporter") == 0)
+-        return append_text(result, "reporter", PACKAGE"-"VERSION, print_item_name);
+-
+-    /* %oneline,%multiline,%text */
+-    bool oneline   = (strcmp(item_name+1, "oneline"  ) == 0);
+-    bool multiline = (strcmp(item_name+1, "multiline") == 0);
+-    bool text      = (strcmp(item_name+1, "text"     ) == 0);
+-    if (!oneline && !multiline && !text)
+-    {
+-        log("Unknown or unsupported element specifier '%s'", item_name);
+-        return 0; /* "I did not print anything" */
+-    }
+-
+-    int printed = 0;
+-
+-    /* Iterate over _sorted_ items */
+-    GList *sorted_names = g_hash_table_get_keys(pd);
+-    sorted_names = g_list_sort(sorted_names, (GCompareFunc)strcmp);
+-
+-    /* %text => do as if %oneline, then repeat as if %multiline */
+-    if (text)
+-        oneline = 1;
+-
+- again: ;
+-    GList *l = sorted_names;
+-    while (l)
+-    {
+-        const char *name = l->data;
+-        l = l->next;
+-        struct problem_item *item = g_hash_table_lookup(pd, name);
+-        if (!item)
+-            continue; /* paranoia, won't happen */
+-
+-        if (!(item->flags & CD_FLAG_TXT))
+-            continue;
+-
+-        if (is_explicit_or_forbidden(name, comment_fmt_spec))
+-            continue;
+-
+-        char *formatted = problem_item_format(item);
+-        char *content = formatted ? formatted : item->content;
+-        char *eol = strchrnul(content, '\n');
+-        bool is_oneline = (eol[0] == '\0' || eol[1] == '\0');
+-        if (oneline == is_oneline)
+-            printed |= append_text(result, name, content, print_item_name);
+-        free(formatted);
+-    }
+-    if (text && oneline)
+-    {
+-        /* %text, and we just did %oneline. Repeat as if %multiline */
+-        oneline = 0;
+-        /*multiline = 1; - not checked in fact, so why bother setting? */
+-        goto again;
+-    }
+-
+-    g_list_free(sorted_names); /* names themselves are not freed */
+-
+-    return printed;
+-}
+-
+-static
+-void generate_bz_comment(struct strbuf *result, problem_data_t *pd, GList *comment_fmt_spec)
+-{
+-    bool last_line_is_empty = true;
+-    GList *l = comment_fmt_spec;
+-    while (l)
+-    {
+-        section_t *sec = l->data;
+-        l = l->next;
+-
+-        /* Skip special sections such as "%attach" */
+-        if (sec->name[0] == '%')
+-            continue;
+-
+-        if (sec->items)
+-        {
+-            /* "Text: item[,item]..." */
+-            struct strbuf *output = strbuf_new();
+-            GList *item = sec->items;
+-            while (item)
+-            {
+-                const char *str = item->data;
+-                item = item->next;
+-                if (str[0] == '-') /* "-name", ignore it */
+-                    continue;
+-                append_item(output, str, pd, comment_fmt_spec);
+-            }
+-
+-            if (output->len != 0)
+-            {
+-                strbuf_append_strf(result,
+-                            sec->name[0] ? "%s:\n%s" : "%s%s",
+-                            sec->name,
+-                            output->buf
+-                );
+-                last_line_is_empty = false;
+-            }
+-            strbuf_free(output);
+-        }
+-        else
+-        {
+-            /* Just "Text" (can be "") */
+-
+-            /* Filter out consecutive empty lines */
+-            if (sec->name[0] != '\0' || !last_line_is_empty)
+-                strbuf_append_strf(result, "%s\n", sec->name);
+-            last_line_is_empty = (sec->name[0] == '\0');
+-        }
+-    }
+-
+-    /* Nuke any trailing empty lines */
+-    while (result->len >= 1
+-     && result->buf[result->len-1] == '\n'
+-     && (result->len == 1 || result->buf[result->len-2] == '\n')
+-    ) {
+-        result->buf[--result->len] = '\0';
+-    }
+-}
+-
+-
+ /* BZ attachments */
+ 
+ static
+@@ -573,104 +69,6 @@ int attach_file_item(struct abrt_xmlrpc *ax, const char *bug_id,
+     return (r == 0);
+ }
+ 
+-static
+-int attach_item(struct abrt_xmlrpc *ax, const char *bug_id,
+-                const char *item_name, problem_data_t *pd, GList *comment_fmt_spec)
+-{
+-    if (item_name[0] != '%')
+-    {
+-        struct problem_item *item = problem_data_get_item_or_NULL(pd, item_name);
+-        if (!item)
+-            return 0;
+-        if (item->flags & CD_FLAG_TXT)
+-            return attach_text_item(ax, bug_id, item_name, item);
+-        if (item->flags & CD_FLAG_BIN)
+-            return attach_file_item(ax, bug_id, item_name, item);
+-        return 0;
+-    }
+-
+-    /* Special item name */
+-
+-    /* %oneline,%multiline,%text,%binary */
+-    bool oneline   = (strcmp(item_name+1, "oneline"  ) == 0);
+-    bool multiline = (strcmp(item_name+1, "multiline") == 0);
+-    bool text      = (strcmp(item_name+1, "text"     ) == 0);
+-    bool binary    = (strcmp(item_name+1, "binary"   ) == 0);
+-    if (!oneline && !multiline && !text && !binary)
+-    {
+-        log("Unknown or unsupported element specifier '%s'", item_name);
+-        return 0;
+-    }
+-
+-    log_debug("Special item_name '%s', iterating for attach...", item_name);
+-    int done = 0;
+-
+-    /* Iterate over _sorted_ items */
+-    GList *sorted_names = g_hash_table_get_keys(pd);
+-    sorted_names = g_list_sort(sorted_names, (GCompareFunc)strcmp);
+-
+-    GList *l = sorted_names;
+-    while (l)
+-    {
+-        const char *name = l->data;
+-        l = l->next;
+-        struct problem_item *item = g_hash_table_lookup(pd, name);
+-        if (!item)
+-            continue; /* paranoia, won't happen */
+-
+-        if (is_explicit_or_forbidden(name, comment_fmt_spec))
+-            continue;
+-
+-        if ((item->flags & CD_FLAG_TXT) && !binary)
+-        {
+-            char *content = item->content;
+-            char *eol = strchrnul(content, '\n');
+-            bool is_oneline = (eol[0] == '\0' || eol[1] == '\0');
+-            if (text || oneline == is_oneline)
+-                done |= attach_text_item(ax, bug_id, name, item);
+-        }
+-        if ((item->flags & CD_FLAG_BIN) && binary)
+-            done |= attach_file_item(ax, bug_id, name, item);
+-    }
+-
+-    g_list_free(sorted_names); /* names themselves are not freed */
+-
+-
+-    log_debug("...Done iterating over '%s' for attach", item_name);
+-
+-    return done;
+-}
+-
+-static
+-int attach_files(struct abrt_xmlrpc *ax, const char *bug_id,
+-                problem_data_t *pd, GList *comment_fmt_spec)
+-{
+-    int done = 0;
+-    GList *l = comment_fmt_spec;
+-    while (l)
+-    {
+-        section_t *sec = l->data;
+-        l = l->next;
+-
+-        /* Find %attach" */
+-        if (strcmp(sec->name, "%attach") != 0)
+-            continue;
+-
+-        GList *item = sec->items;
+-        while (item)
+-        {
+-            const char *str = item->data;
+-            item = item->next;
+-            if (str[0] == '-') /* "-name", ignore it */
+-                continue;
+-            done |= attach_item(ax, bug_id, str, pd, comment_fmt_spec);
+-        }
+-    }
+-
+-    return done;
+-}
+-
+-
+ /* Main */
+ 
+ struct bugzilla_struct {
+@@ -1102,18 +500,29 @@ int main(int argc, char **argv)
+ 
+     if (opts & OPT_D)
+     {
+-        GList *comment_fmt_spec = load_bzrep_conf_file(fmt_file);
+-        struct strbuf *bzcomment_buf = strbuf_new();
+-        generate_bz_comment(bzcomment_buf, problem_data, comment_fmt_spec);
+-        char *bzcomment = strbuf_free_nobuf(bzcomment_buf);
+-        char *summary = create_summary_string(problem_data, comment_fmt_spec);
++        problem_formatter_t *pf = problem_formatter_new();
++
++        if (problem_formatter_load_file(pf, fmt_file))
++            error_msg_and_die("Invalid format file: %s", fmt_file);
++
++        problem_report_t *pr = NULL;
++        if (problem_formatter_generate_report(pf, problem_data, &pr))
++            error_msg_and_die("Failed to format bug report from problem data");
++
+         printf("summary: %s\n"
+                 "\n"
+                 "%s"
+-                , summary, bzcomment
++                "\n"
++                , problem_report_get_summary(pr)
++                , problem_report_get_description(pr)
+         );
+-        free(bzcomment);
+-        free(summary);
++
++        puts("attachments:");
++        for (GList *a = problem_report_get_attachments(pr); a != NULL; a = g_list_next(a))
++            printf(" %s\n", (const char *)a->data);
++
++        problem_report_free(pr);
++        problem_formatter_free(pf);
+         exit(0);
+     }
+ 
+@@ -1196,22 +605,29 @@ int main(int argc, char **argv)
+             /* Create new bug */
+             log(_("Creating a new bug"));
+ 
+-            GList *comment_fmt_spec = load_bzrep_conf_file(fmt_file);
++            problem_formatter_t *pf = problem_formatter_new();
++
++            if (problem_formatter_load_file(pf, fmt_file))
++                error_msg_and_die("Invalid format file: %s", fmt_file);
++
++            problem_report_t *pr = NULL;
++            if (problem_formatter_generate_report(pf, problem_data, &pr))
++                error_msg_and_die("Failed to format problem data");
+ 
+-            struct strbuf *bzcomment_buf = strbuf_new();
+-            generate_bz_comment(bzcomment_buf, problem_data, comment_fmt_spec);
+             if (crossver_id >= 0)
+-                strbuf_append_strf(bzcomment_buf, "\nPotential duplicate: bug %u\n", crossver_id);
+-            char *bzcomment = strbuf_free_nobuf(bzcomment_buf);
+-            char *summary = create_summary_string(problem_data, comment_fmt_spec);
++                problem_report_buffer_printf(
++                        problem_report_get_buffer(pr, PR_SEC_DESCRIPTION),
++                        "\nPotential duplicate: bug %u\n", crossver_id);
++
++            problem_formatter_free(pf);
++
+             int new_id = rhbz_new_bug(client,
+                     problem_data, rhbz.b_product, rhbz.b_product_version,
+-                    summary, bzcomment,
++                    problem_report_get_summary(pr),
++                    problem_report_get_description(pr),
+                     (rhbz.b_create_private | (opts & OPT_g)), // either we got Bugzilla_CreatePrivate from settings or -g was specified on cmdline
+                     rhbz.b_private_groups
+                     );
+-            free(bzcomment);
+-            free(summary);
+ 
+             if (new_id == -1)
+             {
+@@ -1236,9 +652,17 @@ int main(int argc, char **argv)
+             char new_id_str[sizeof(int)*3 + 2];
+             sprintf(new_id_str, "%i", new_id);
+ 
+-            attach_files(client, new_id_str, problem_data, comment_fmt_spec);
+-
+-//TODO: free_comment_fmt_spec(comment_fmt_spec);
++            for (GList *a = problem_report_get_attachments(pr); a != NULL; a = g_list_next(a))
++            {
++                const char *item_name = (const char *)a->data;
++                struct problem_item *item = problem_data_get_item_or_NULL(problem_data, item_name);
++                if (!item)
++                    continue;
++                else if (item->flags & CD_FLAG_TXT)
++                    attach_text_item(client, new_id_str, item_name, item);
++                else if (item->flags & CD_FLAG_BIN)
++                    attach_file_item(client, new_id_str, item_name, item);
++            }
+ 
+             bz = new_bug_info();
+             bz->bi_status = xstrdup("NEW");
+@@ -1302,18 +726,21 @@ int main(int argc, char **argv)
+     const char *comment = problem_data_get_content_or_NULL(problem_data, FILENAME_COMMENT);
+     if (comment && comment[0])
+     {
+-        GList *comment_fmt_spec = load_bzrep_conf_file(fmt_file2);
+-        struct strbuf *bzcomment_buf = strbuf_new();
+-        generate_bz_comment(bzcomment_buf, problem_data, comment_fmt_spec);
+-        char *bzcomment = strbuf_free_nobuf(bzcomment_buf);
+-//TODO: free_comment_fmt_spec(comment_fmt_spec);
++        problem_formatter_t *pf = problem_formatter_new();
++        if (problem_formatter_load_file(pf, fmt_file2))
++            error_msg_and_die("Invalid duplicate format file: '%s", fmt_file2);
++
++        problem_report_t *pr;
++        if (problem_formatter_generate_report(pf, problem_data, &pr))
++            error_msg_and_die("Failed to format duplicate comment from problem data");
++
++        const char *bzcomment = problem_report_get_description(pr);
+ 
+         int dup_comment = is_comment_dup(bz->bi_comments, bzcomment);
+         if (!dup_comment)
+         {
+             log(_("Adding new comment to bug %d"), bz->bi_id);
+             rhbz_add_comment(client, bz->bi_id, bzcomment, 0);
+-            free(bzcomment);
+ 
+             const char *bt = problem_data_get_content_or_NULL(problem_data, FILENAME_BACKTRACE);
+             unsigned rating = 0;
+@@ -1331,10 +758,10 @@ int main(int argc, char **argv)
+             }
+         }
+         else
+-        {
+-            free(bzcomment);
+             log(_("Found the same comment in the bug history, not adding a new one"));
+-        }
++
++        problem_report_free(pr);
++        problem_formatter_free(pf);
+     }
+ 
+  log_out:
+-- 
+1.8.3.1
+
diff --git a/SOURCES/0130-lib-created-a-new-lib-file-for-reporters.patch b/SOURCES/0130-lib-created-a-new-lib-file-for-reporters.patch
new file mode 100644
index 0000000..7a20cbc
--- /dev/null
+++ b/SOURCES/0130-lib-created-a-new-lib-file-for-reporters.patch
@@ -0,0 +1,405 @@
+From 5a6e03181d06d9350f181d21ab103bb0c07ffb86 Mon Sep 17 00:00:00 2001
+From: Matej Habrnal <mhabrnal@redhat.com>
+Date: Tue, 13 Jan 2015 19:23:08 -0500
+Subject: [PATCH 130/136] lib: created a new lib file for reporters
+
+Moved some functions from rhbz.c to src/lib/reporters.c and src/lib/strbuf.c.
+
+Related to #272
+
+Signed-off-by: Matej Habrnal <mhabrnal@redhat.com>
+---
+ po/POTFILES.in                   |  1 +
+ src/include/Makefile.am          |  3 +-
+ src/include/internal_libreport.h |  5 +++
+ src/include/reporters.h          | 36 ++++++++++++++++++
+ src/lib/Makefile.am              |  3 +-
+ src/lib/reporters.c              | 80 ++++++++++++++++++++++++++++++++++++++++
+ src/lib/strbuf.c                 | 60 ++++++++++++++++++++++++++++++
+ src/plugins/rhbz.c               | 78 +--------------------------------------
+ src/plugins/rhbz.h               |  2 -
+ 9 files changed, 188 insertions(+), 80 deletions(-)
+ create mode 100644 src/include/reporters.h
+ create mode 100644 src/lib/reporters.c
+
+diff --git a/po/POTFILES.in b/po/POTFILES.in
+index c597b11..5588540 100644
+--- a/po/POTFILES.in
++++ b/po/POTFILES.in
+@@ -24,6 +24,7 @@ src/lib/make_descr.c
+ src/lib/parse_options.c
+ src/lib/problem_data.c
+ src/lib/problem_report.c
++src/lib/reporters.c
+ src/lib/run_event.c
+ src/plugins/abrt_rh_support.c
+ src/plugins/report_Bugzilla.xml.in
+diff --git a/src/include/Makefile.am b/src/include/Makefile.am
+index 47ba399..a13e04d 100644
+--- a/src/include/Makefile.am
++++ b/src/include/Makefile.am
+@@ -15,7 +15,8 @@ libreport_include_HEADERS = \
+     file_obj.h \
+     internal_libreport.h \
+     internal_abrt_dbus.h \
+-    xml_parser.h
++    xml_parser.h \
++    reporters.h
+ 
+ if BUILD_UREPORT
+ libreport_include_HEADERS += ureport.h
+diff --git a/src/include/internal_libreport.h b/src/include/internal_libreport.h
+index 967324b..b689b74 100644
+--- a/src/include/internal_libreport.h
++++ b/src/include/internal_libreport.h
+@@ -98,6 +98,7 @@ int vdprintf(int d, const char *format, va_list ap);
+ #include "workflow.h"
+ #include "file_obj.h"
+ #include "libreport_types.h"
++#include "reporters.h"
+ 
+ #ifdef __cplusplus
+ extern "C" {
+@@ -107,6 +108,10 @@ extern "C" {
+ int prefixcmp(const char *str, const char *prefix);
+ #define suffixcmp libreport_suffixcmp
+ int suffixcmp(const char *str, const char *suffix);
++#define trim_all_whitespace libreport_trim_all_whitespace
++char *trim_all_whitespace(const char *str);
++#define shorten_string_to_length libreport_shorten_string_to_length
++char *shorten_string_to_length(const char *str, unsigned length);
+ #define strtrim libreport_strtrim
+ char *strtrim(char *str);
+ #define strtrimch libreport_strtrimch
+diff --git a/src/include/reporters.h b/src/include/reporters.h
+new file mode 100644
+index 0000000..d415b7f
+--- /dev/null
++++ b/src/include/reporters.h
+@@ -0,0 +1,36 @@
++/*
++    Copyright (C) 2014  ABRT team
++    Copyright (C) 2014  RedHat Inc
++
++    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 2 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, write to the Free Software Foundation, Inc.,
++    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
++*/
++
++#ifndef REPORTERS_H
++#define REPORTERS_H
++
++#ifdef __cplusplus
++extern "C" {
++#endif
++
++#define is_comment_dup libreport_is_comment_dup
++int is_comment_dup(GList *comments, const char *comment);
++#define comments_find_best_bt_rating libreport_comments_find_best_bt_rating
++unsigned comments_find_best_bt_rating(GList *comments);
++
++#ifdef __cplusplus
++}
++#endif
++
++#endif
+diff --git a/src/lib/Makefile.am b/src/lib/Makefile.am
+index a0001ef..3ec463f 100644
+--- a/src/lib/Makefile.am
++++ b/src/lib/Makefile.am
+@@ -56,7 +56,8 @@ libreport_la_SOURCES = \
+     workflow_xml_parser.c \
+     config_item_info.c \
+     xml_parser.c \
+-    libreport_init.c
++    libreport_init.c \
++    reporters.c
+ 
+ libreport_la_CPPFLAGS = \
+     -I$(srcdir)/../include \
+diff --git a/src/lib/reporters.c b/src/lib/reporters.c
+new file mode 100644
+index 0000000..e3305ca
+--- /dev/null
++++ b/src/lib/reporters.c
+@@ -0,0 +1,80 @@
++/*
++    String buffer implementation
++
++    Copyright (C) 2015  RedHat inc.
++
++    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 2 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, write to the Free Software Foundation, Inc.,
++    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
++*/
++
++#include "internal_libreport.h"
++
++int
++is_comment_dup(GList *comments, const char *comment)
++{
++    char * const trim_comment = trim_all_whitespace(comment);
++    bool same_comments = false;
++
++    for (GList *l = comments; l && !same_comments; l = l->next)
++    {
++        const char * const comment_body = (const char *) l->data;
++        char * const trim_comment_body = trim_all_whitespace(comment_body);
++        same_comments = (strcmp(trim_comment_body, trim_comment) == 0);
++        free(trim_comment_body);
++    }
++
++    free(trim_comment);
++    return same_comments;
++}
++
++unsigned
++comments_find_best_bt_rating(GList *comments)
++{
++    if (comments == NULL)
++        return 0;
++
++    unsigned best_rating = 0;
++    for (GList *l = comments; l; l = l->next)
++    {
++        char *comment_body = (char *) l->data;
++
++        char *start_rating_line = strstr(comment_body, FILENAME_RATING": ");
++        if (!start_rating_line)
++        {
++            log_debug(_("Note does not contain rating"));
++            continue;
++        }
++
++        start_rating_line += strlen(FILENAME_RATING": ");
++
++        errno = 0;
++        char *e;
++        long rating = strtoul(start_rating_line, &e, 10);
++        /*
++         * Note: we intentionally check for '\n'. Any other terminator
++         * (even '\0') is not ok in this case.
++         */
++        if (errno || e == start_rating_line || (*e != '\n' && *e != '\r') || (unsigned long)rating > UINT_MAX)
++        {
++            /* error / no digits / illegal trailing chars */
++            continue;
++        }
++
++        if (rating > best_rating)
++            best_rating = rating;
++    }
++
++    return best_rating;
++}
++
+diff --git a/src/lib/strbuf.c b/src/lib/strbuf.c
+index ef8bda8..f0cd1b8 100644
+--- a/src/lib/strbuf.c
++++ b/src/lib/strbuf.c
+@@ -37,6 +37,66 @@ int suffixcmp(const char *str, const char *suffix)
+         return strcmp(str + len_minus_suflen, suffix);
+ }
+ 
++char *trim_all_whitespace(const char *str)
++{
++    char *trim = xzalloc(sizeof(char) * strlen(str) + 1);
++    int i = 0;
++    while (*str)
++    {
++        if (!isspace(*str))
++            trim[i++] = *str;
++        str++;
++    }
++
++    return trim;
++}
++
++/* If str is longer than max allowed length then
++ * try to find first ' ' from the end of acceptable long str string
++ *
++ * If ' ' is found replace string after that by "..."
++ *
++ * If ' ' is NOT found in maximal allowed range, cut str string on
++ * lenght (MAX_SUMMARY_LENGTH - strlen("...")) and append "..."
++ *
++ * If MAX_LENGTH is 15 and max allowed cut is 5:
++ *
++ *   0123456789ABCDEF -> 0123456789AB...
++ *   0123456789 BCDEF -> 0123456789 ...
++ *   012345 789ABCDEF -> 012345 789AB...
++ */
++char *
++shorten_string_to_length(const char *str, unsigned length)
++{
++    char *dup_str = xstrdup(str);
++    if (strlen(str) > length)
++    {
++        char *max_end = dup_str + (length - strlen("..."));
++
++        /* maximal number of characters to cut due to attempt cut dup_str
++         * string after last ' '
++         */
++        int max_cut = 16;
++
++        /* start looking for ' ' one char before the last possible character */
++        char *buf = max_end - 1;
++        while (buf[0] != ' ' && max_cut--)
++            --buf;
++
++        if (buf[0] != ' ')
++            buf = max_end;
++        else
++            ++buf;
++
++        buf[0] = '.';
++        buf[1] = '.';
++        buf[2] = '.';
++        buf[3] = '\0';
++    }
++
++    return dup_str;
++}
++
+ /*
+  * Trims whitespace characters both from left and right side of a string.
+  * Modifies the string in-place. Returns the trimmed string.
+diff --git a/src/plugins/rhbz.c b/src/plugins/rhbz.c
+index bad9ed4..a376c13 100644
+--- a/src/plugins/rhbz.c
++++ b/src/plugins/rhbz.c
+@@ -133,41 +133,6 @@ static GList *rhbz_comments(struct abrt_xmlrpc *ax, int bug_id)
+     return g_list_reverse(comments);
+ }
+ 
+-static char *trim_all_whitespace(const char *str)
+-{
+-    func_entry();
+-
+-    char *trim = xzalloc(sizeof(char) * strlen(str) + 1);
+-    int i = 0;
+-    while (*str)
+-    {
+-        if (!isspace(*str))
+-            trim[i++] = *str;
+-        str++;
+-    }
+-
+-    return trim;
+-}
+-
+-int is_comment_dup(GList *comments, const char *comment)
+-{
+-    func_entry();
+-
+-    char * const trim_comment = trim_all_whitespace(comment);
+-    bool same_comments = false;
+-
+-    for (GList *l = comments; l && !same_comments; l = l->next)
+-    {
+-        const char * const comment_body = (const char *) l->data;
+-        char * const trim_comment_body = trim_all_whitespace(comment_body);
+-        same_comments = (strcmp(trim_comment_body, trim_comment) == 0);
+-        free(trim_comment_body);
+-    }
+-
+-    free(trim_comment);
+-    return same_comments;
+-}
+-
+ static unsigned find_best_bt_rating_in_comments(GList *comments)
+ {
+     func_entry();
+@@ -553,46 +518,7 @@ int rhbz_new_bug(struct abrt_xmlrpc *ax,
+     if (!duphash) duphash    = problem_data_get_content_or_NULL(problem_data,
+                                                                 "global_uuid");
+ 
+-    /* If summary is longer than max allowed summary length then
+-     * try to find first ' ' from the end of acceptable long summary string
+-     *
+-     * If ' ' is found replace string after that by "..."
+-     *
+-     * If ' ' is NOT found in maximal allowed range, cut summary string on
+-     * lenght (MAX_SUMMARY_LENGTH - strlen("...")) and append "..."
+-     *
+-     * If MAX_SUMMARY_LENGTH is 15 and max allowed cut is 5:
+-     *
+-     *   0123456789ABCDEF -> 0123456789AB...
+-     *   0123456789 BCDEF -> 0123456789 ...
+-     *   012345 789ABCDEF -> 012345 789AB...
+-     */
+-    char *summary = NULL;
+-    if (strlen(bzsummary) > MAX_SUMMARY_LENGTH)
+-    {
+-        summary = xstrdup(bzsummary);
+-        char *max_end = summary + (MAX_SUMMARY_LENGTH - strlen("..."));
+-
+-        /* maximal number of characters to cut due to attempt cut summary
+-         * string after last ' '
+-         */
+-        int max_cut = 16;
+-
+-        /* start looking for ' ' one char before the last possible character */
+-        char *buf = max_end - 1;
+-        while (buf[0] != ' ' && max_cut--)
+-            --buf;
+-
+-        if (buf[0] != ' ')
+-            buf = max_end;
+-        else
+-            ++buf;
+-
+-        buf[0] = '.';
+-        buf[1] = '.';
+-        buf[2] = '.';
+-        buf[3] = '\0';
+-    }
++    char *summary = shorten_string_to_length(bzsummary, MAX_SUMMARY_LENGTH);
+ 
+     char *status_whiteboard = xasprintf("abrt_hash:%s", duphash);
+ 
+@@ -604,7 +530,7 @@ int rhbz_new_bug(struct abrt_xmlrpc *ax,
+     abrt_xmlrpc_params_add_string(&env, params, "product", product);
+     abrt_xmlrpc_params_add_string(&env, params, "component", component);
+     abrt_xmlrpc_params_add_string(&env, params, "version", version);
+-    abrt_xmlrpc_params_add_string(&env, params, "summary", (summary ? summary : bzsummary));
++    abrt_xmlrpc_params_add_string(&env, params, "summary", summary);
+     abrt_xmlrpc_params_add_string(&env, params, "description", bzcomment);
+     abrt_xmlrpc_params_add_string(&env, params, "status_whiteboard", status_whiteboard);
+ 
+diff --git a/src/plugins/rhbz.h b/src/plugins/rhbz.h
+index 976d333..2f91962 100644
+--- a/src/plugins/rhbz.h
++++ b/src/plugins/rhbz.h
+@@ -101,8 +101,6 @@ int rhbz_attach_blob(struct abrt_xmlrpc *ax, const char *bug_id,
+ int rhbz_attach_fd(struct abrt_xmlrpc *ax, const char *bug_id,
+                 const char *att_name, int fd, int flags);
+ 
+-int is_comment_dup(GList *comments, const char *comment);
+-
+ GList *rhbz_bug_cc(xmlrpc_value *result_xml);
+ 
+ struct bug_info *rhbz_bug_info(struct abrt_xmlrpc *ax, int bug_id);
+-- 
+1.8.3.1
+
diff --git a/SOURCES/0133-ureport-set-url-to-public-faf-server.patch b/SOURCES/0133-ureport-set-url-to-public-faf-server.patch
new file mode 100644
index 0000000..0d1e8f6
--- /dev/null
+++ b/SOURCES/0133-ureport-set-url-to-public-faf-server.patch
@@ -0,0 +1,27 @@
+From 9f11449cd89e87b932bf47e264e00f36582b3a9b Mon Sep 17 00:00:00 2001
+From: Matej Habrnal <mhabrnal@redhat.com>
+Date: Mon, 2 Feb 2015 16:31:51 +0100
+Subject: [PATCH 133/136] ureport: set url to public faf server
+
+Set url to public faf server because the private one doesn't return URL to
+known issues, bthash, possible solution etc.
+
+Signed-off-by: Matej Habrnal <mhabrnal@redhat.com>
+---
+ src/plugins/ureport.conf | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/src/plugins/ureport.conf b/src/plugins/ureport.conf
+index e04bf56..4d64ee7 100644
+--- a/src/plugins/ureport.conf
++++ b/src/plugins/ureport.conf
+@@ -1,5 +1,5 @@
+ # Base URL to uReport server
+-# URL = http://bug-report.itos.redhat.com
++URL = https://retrace.fedoraproject.org/faf
+ 
+ # no means that ssl certificates will not be checked
+ # SSLVerify = no
+-- 
+1.8.3.1
+
diff --git a/SOURCES/0134-conf-changed-URL-for-sending-uReport.patch b/SOURCES/0134-conf-changed-URL-for-sending-uReport.patch
new file mode 100644
index 0000000..424f5ef
--- /dev/null
+++ b/SOURCES/0134-conf-changed-URL-for-sending-uReport.patch
@@ -0,0 +1,29 @@
+From 8d81afa9fdd7beeac1f4fa20073af599713e071e Mon Sep 17 00:00:00 2001
+From: Matej Habrnal <mhabrnal@redhat.com>
+Date: Mon, 2 Feb 2015 21:41:36 +0100
+Subject: [PATCH 134/136] conf: changed URL for sending uReport
+
+Changed faf server url in report_uReport.xml.in.
+uReports are sending to https://retrace.fedoraproject.org/faf by default.
+
+Signed-off-by: Matej Habrnal <mhabrnal@redhat.com>
+---
+ src/plugins/report_uReport.xml.in | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/src/plugins/report_uReport.xml.in b/src/plugins/report_uReport.xml.in
+index 63dfc22..aca857a 100644
+--- a/src/plugins/report_uReport.xml.in
++++ b/src/plugins/report_uReport.xml.in
+@@ -11,7 +11,7 @@
+             <_label>uReport Server URL</_label>
+             <allow-empty>no</allow-empty>
+             <_description>Address of uReport webservice</_description>
+-            <default-value>http://bug-report.itos.redhat.com</default-value>
++            <default-value>https://retrace.fedoraproject.org/faf</default-value>
+         </option>
+         <option type="text" name="uReport_ContactEmail">
+             <_label>Contact email address</_label>
+-- 
+1.8.3.1
+
diff --git a/SOURCES/1000-reporter-mantisbt-first-version-of-the-reporter-mant.patch b/SOURCES/1000-reporter-mantisbt-first-version-of-the-reporter-mant.patch
new file mode 100644
index 0000000..8d80929
--- /dev/null
+++ b/SOURCES/1000-reporter-mantisbt-first-version-of-the-reporter-mant.patch
@@ -0,0 +1,2659 @@
+From 220e7bb13328371bf5020da1d43a26871d91b96e Mon Sep 17 00:00:00 2001
+From: Matej Habrnal <mhabrnal@redhat.com>
+Date: Tue, 13 Jan 2015 19:30:27 -0500
+Subject: [PATCH 1000/1001] reporter-mantisbt: first version of the
+ reporter-mantisbt
+
+Related to #272
+
+Signed-off-by: Matej Habrnal <mhabrnal@redhat.com>
+---
+ configure.ac                                   |   33 +
+ po/POTFILES.in                                 |   11 +
+ src/plugins/Makefile.am                        |   43 +-
+ src/plugins/centos_report_event.conf           |   37 +
+ src/plugins/mantisbt.c                         | 1111 ++++++++++++++++++++++++
+ src/plugins/mantisbt.conf                      |    8 +
+ src/plugins/mantisbt.h                         |  140 +++
+ src/plugins/mantisbt_format.conf               |   59 ++
+ src/plugins/mantisbt_formatdup.conf            |   65 ++
+ src/plugins/report_CentOSBugTracker.conf       |    4 +
+ src/plugins/report_CentOSBugTracker.xml.in     |   65 ++
+ src/plugins/reporter-mantisbt.c                |  696 +++++++++++++++
+ src/workflows/Makefile.am                      |   15 +-
+ src/workflows/report_centos.conf               |   31 +
+ src/workflows/workflow_CentOSCCpp.xml.in       |   12 +
+ src/workflows/workflow_CentOSJava.xml.in       |   11 +
+ src/workflows/workflow_CentOSKerneloops.xml.in |   11 +
+ src/workflows/workflow_CentOSLibreport.xml.in  |    9 +
+ src/workflows/workflow_CentOSPython.xml.in     |   11 +
+ src/workflows/workflow_CentOSPython3.xml.in    |   11 +
+ src/workflows/workflow_CentOSVmcore.xml.in     |   12 +
+ src/workflows/workflow_CentOSXorg.xml.in       |    9 +
+ 22 files changed, 2402 insertions(+), 2 deletions(-)
+ create mode 100644 src/plugins/centos_report_event.conf
+ create mode 100644 src/plugins/mantisbt.c
+ create mode 100644 src/plugins/mantisbt.conf
+ create mode 100644 src/plugins/mantisbt.h
+ create mode 100644 src/plugins/mantisbt_format.conf
+ create mode 100644 src/plugins/mantisbt_formatdup.conf
+ create mode 100644 src/plugins/report_CentOSBugTracker.conf
+ create mode 100644 src/plugins/report_CentOSBugTracker.xml.in
+ create mode 100644 src/plugins/reporter-mantisbt.c
+ create mode 100644 src/workflows/report_centos.conf
+ create mode 100644 src/workflows/workflow_CentOSCCpp.xml.in
+ create mode 100644 src/workflows/workflow_CentOSJava.xml.in
+ create mode 100644 src/workflows/workflow_CentOSKerneloops.xml.in
+ create mode 100644 src/workflows/workflow_CentOSLibreport.xml.in
+ create mode 100644 src/workflows/workflow_CentOSPython.xml.in
+ create mode 100644 src/workflows/workflow_CentOSPython3.xml.in
+ create mode 100644 src/workflows/workflow_CentOSVmcore.xml.in
+ create mode 100644 src/workflows/workflow_CentOSXorg.xml.in
+
+diff --git a/configure.ac b/configure.ac
+index df88240..692ee81 100644
+--- a/configure.ac
++++ b/configure.ac
+@@ -131,6 +131,39 @@ do
+ done
+ fi dnl end NO_BUGZILLA
+ 
++AC_ARG_WITH(mantisbt,
++AS_HELP_STRING([--with-mantisbt],[use MantisBT plugin (default is YES)]),
++LIBREPORT_PARSE_WITH([mantisbt]))
++
++if test -z "$NO_MANTISBT"; then
++AM_CONDITIONAL(BUILD_MANTISBT, true)
++
++# enable mantisbt & deps translations
++for FILE in `grep -e "#.*antisbt.*" -e "#.*naconda.*" po/POTFILES.in`
++do
++  sed -ie "s,$FILE,${FILE:1}," po/POTFILES.in
++  sed -ie "\,^${FILE:1}$,d" po/POTFILES.skip
++done
++else
++AM_CONDITIONAL(BUILD_MANTISBT, false)
++
++# disablie mantisbt & deps translations
++for FILE in `grep -e "antisbt" -e "naconda" po/POTFILES.in`
++do
++  if test "${FILE:0:1}" = "#"
++  then
++    continue
++  fi
++
++  sed -ie "s,$FILE,#$FILE," po/POTFILES.in
++  grep "$FILE" po/POTFILES.skip > /dev/null 2>&1
++  if test $?
++  then
++    echo "$FILE" >> po/POTFILES.skip
++  fi
++done
++fi dnl end NO_MANTISBT
++
+ AC_PATH_PROG([PYTHON_CONFIG], [python-config], [no])
+ [if test "$PYTHON_CONFIG" = "no"]
+ [then]
+diff --git a/po/POTFILES.in b/po/POTFILES.in
+index 5588540..3415b03 100644
+--- a/po/POTFILES.in
++++ b/po/POTFILES.in
+@@ -36,6 +36,8 @@ src/plugins/reporter-print.c
+ src/plugins/reporter-rhtsupport.c
+ src/plugins/reporter-rhtsupport-parse.c
+ src/plugins/reporter-upload.c
++src/plugins/reporter-mantisbt.c
++src/plugins/report_CentOSBugTracker.xml.in
+ src/plugins/report_Kerneloops.xml.in
+ src/plugins/report_Logger.xml.in
+ src/plugins/report_Mailx.xml.in
+@@ -44,12 +46,21 @@ src/plugins/report_Uploader.xml.in
+ src/plugins/report_uReport.xml.in
+ src/plugins/report_EmergencyAnalysis.xml.in
+ src/plugins/rhbz.c
++src/plugins/mantisbt.c
+ src/plugins/reporter-ureport.c
+ src/report-newt/report-newt.c
+ src/workflows/workflow_AnacondaFedora.xml.in
+ src/workflows/workflow_AnacondaRHEL.xml.in
+ src/workflows/workflow_AnacondaRHELBugzilla.xml.in
+ src/workflows/workflow_AnacondaUpload.xml.in
++src/workflows/workflow_CentOSCCpp.xml.in
++src/workflows/workflow_CentOSJava.xml.in
++src/workflows/workflow_CentOSKerneloops.xml.in
++src/workflows/workflow_CentOSLibreport.xml.in
++src/workflows/workflow_CentOSPython.xml.in
++src/workflows/workflow_CentOSPython3.xml.in
++src/workflows/workflow_CentOSVmcore.xml.in
++src/workflows/workflow_CentOSXorg.xml.in
+ src/workflows/workflow_FedoraCCpp.xml.in
+ src/workflows/workflow_FedoraKerneloops.xml.in
+ src/workflows/workflow_FedoraPython.xml.in
+diff --git a/src/plugins/Makefile.am b/src/plugins/Makefile.am
+index 7ec08d7..d70b4db 100644
+--- a/src/plugins/Makefile.am
++++ b/src/plugins/Makefile.am
+@@ -7,6 +7,11 @@ reporters_bin += \
+     report
+ endif
+ 
++if BUILD_MANTISBT
++reporters_bin += \
++    reporter-mantisbt
++endif
++
+ if BUILD_UREPORT
+ reporters_bin +=  reporter-ureport
+ endif
+@@ -34,6 +39,12 @@ reporters_plugin_format_conf += bugzilla_format.conf \
+     bugzilla_formatdup_anaconda.conf
+ endif
+ 
++if BUILD_MANTISBT
++reporters_plugin_conf += mantisbt.conf
++reporters_plugin_format_conf += mantisbt_format.conf \
++    mantisbt_formatdup.conf
++endif
++
+ defaultreportpluginsconfdir = $(DEFAULT_REPORT_PLUGINS_CONF_DIR)
+ dist_defaultreportpluginsconf_DATA = $(reporters_plugin_conf) \
+     rhtsupport.conf \
+@@ -53,6 +64,12 @@ reporters_events += report_Bugzilla.xml
+ reporters_events_conf += report_Bugzilla.conf
+ endif
+ 
++if BUILD_MANTISBT
++reporters_events += report_CentOSBugTracker.xml
++
++reporters_events_conf += report_CentOSBugTracker.conf
++endif
++
+ if BUILD_UREPORT
+ reporters_events += report_uReport.xml
+ endif
+@@ -77,7 +94,8 @@ dist_eventsdef_DATA = \
+     print_event.conf \
+     rhtsupport_event.conf \
+     uploader_event.conf \
+-    emergencyanalysis_event.conf
++    emergencyanalysis_event.conf \
++    centos_report_event.conf
+ 
+ reporters_extra_dist =
+ if BUILD_BUGZILLA
+@@ -125,6 +143,29 @@ reporter_bugzilla_LDADD = \
+     ../lib/libreport.la
+ endif
+ 
++if BUILD_MANTISBT
++reporter_mantisbt_SOURCES = \
++    reporter-mantisbt.c mantisbt.c mantisbt.h
++reporter_mantisbt_CPPFLAGS = \
++    -I$(srcdir)/../include \
++    -I$(srcdir)/../lib \
++    -DBIN_DIR=\"$(bindir)\" \
++    -DCONF_DIR=\"$(CONF_DIR)\" \
++    -DLOCALSTATEDIR='"$(localstatedir)"' \
++    -DDEBUG_DUMPS_DIR=\"$(DEBUG_DUMPS_DIR)\" \
++    -DDEBUG_INFO_DIR=\"$(DEBUG_INFO_DIR)\" \
++    -DPLUGINS_LIB_DIR=\"$(PLUGINS_LIB_DIR)\" \
++    -DPLUGINS_CONF_DIR=\"$(REPORT_PLUGINS_CONF_DIR)\" \
++    $(GLIB_CFLAGS) \
++    $(LIBREPORT_CFLAGS) \
++    $(LIBXML_CFLAGS) \
++    -D_GNU_SOURCE
++reporter_mantisbt_LDADD = \
++    $(GLIB_LIBS) \
++    ../lib/libreport-web.la \
++    ../lib/libreport.la
++endif
++
+ reporter_rhtsupport_SOURCES = \
+     abrt_rh_support.h abrt_rh_support.c \
+     reporter-rhtsupport.h \
+diff --git a/src/plugins/centos_report_event.conf b/src/plugins/centos_report_event.conf
+new file mode 100644
+index 0000000..53f12d8
+--- /dev/null
++++ b/src/plugins/centos_report_event.conf
+@@ -0,0 +1,37 @@
++EVENT=report_CentOSBugTracker analyzer=xorg
++    reporter-mantisbt
++
++EVENT=report_CentOSBugTracker analyzer=Kerneloops
++    reporter-mantisbt
++
++EVENT=report_CentOSBugTracker analyzer=vmcore
++        reporter-mantisbt
++
++EVENT=report_CentOSBugTracker analyzer=Python component!=anaconda
++        test -f component || abrt-action-save-package-data
++        reporter-mantisbt \
++                -c /etc/libreport/plugins/mantisbt.conf \
++                -F /etc/libreport/plugins/mantisbt_format.conf \
++                -A /etc/libreport/plugins/mantisbt_formatdup.conf
++
++EVENT=report_CentOSBugTracker analyzer=Python3 component!=anaconda
++        test -f component || abrt-action-save-package-data
++        reporter-mantisbt \
++                -c /etc/libreport/plugins/mantisbt.conf \
++                -F /etc/libreport/plugins/mantisbt_format.conf \
++                -A /etc/libreport/plugins/mantisbt_formatdup.conf
++
++EVENT=report_CentOSBugTracker analyzer=CCpp duphash!=
++        test -f component || abrt-action-save-package-data
++        component="`cat component`"
++        format="mantisbt_format.conf"
++        test -f "/etc/libreport/plugins/mantisbt_format_$component.conf" \
++                && format="mantisbt_format_$component.conf"
++        formatdup="mantisbt_formatdup.conf"
++        test -f "/etc/libreport/plugins/mantisbt_formatdup_$component.conf" \
++                && formatdup="mantisbt_formatdup_$component.conf"
++        reporter-mantisbt \
++                -c /etc/libreport/plugins/mantisbt.conf \
++                -F "/etc/libreport/plugins/$format" \
++                -A "/etc/libreport/plugins/$formatdup"
++
+diff --git a/src/plugins/mantisbt.c b/src/plugins/mantisbt.c
+new file mode 100644
+index 0000000..1c496b4
+--- /dev/null
++++ b/src/plugins/mantisbt.c
+@@ -0,0 +1,1111 @@
++/*
++    Copyright (C) 2014  ABRT team
++    Copyright (C) 2014  RedHat Inc
++
++    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 2 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, write to the Free Software Foundation, Inc.,
++    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
++*/
++
++#include <curl/curl.h>
++
++#include <libxml/xmlreader.h>
++
++#include "internal_libreport.h"
++#include "libreport_curl.h"
++#include "mantisbt.h"
++#include "client.h"
++
++/*
++ * SOAP
++*/
++
++#define XML_VERSION "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
++
++/* fprint string */
++#define SOAP_TEMPLATE \
++    "<SOAP-ENV:Envelope xmlns:ns3=\"http://schemas.xmlsoap.org/soap/encoding/\" " \
++        "xmlns:SOAP-ENC=\"http://schemas.xmlsoap.org/soap/encoding/\" " \
++        "xmlns:ns0=\"http://schemas.xmlsoap.org/soap/encoding/\" " \
++        "xmlns:ns1=\"http://schemas.xmlsoap.org/soap/envelope/\" " \
++        "xmlns:ns2=\"http://www.w3.org/2001/XMLSchema\" " \
++        "xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" " \
++        "xmlns:SOAP-ENV=\"http://schemas.xmlsoap.org/soap/envelope/\" " \
++        "SOAP-ENV:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">" \
++    "<SOAP-ENV:Header/>" \
++    "<ns1:Body>" \
++        "<ns3:%s>" \
++        "</ns3:%s>" \
++    "</ns1:Body>" \
++    "</SOAP-ENV:Envelope>"
++
++#define MAX_SUMMARY_LENGTH  128
++#define CUSTOMFIELD_DUPHASH "abrt_hash"
++#define CUSTOMFIELD_URL "URL"
++#define MAX_HOPS 5
++
++/* MantisBT limit is 2MB by default
++ */
++#define MANTISBT_MAX_FILE_UPLOAD_SIZE (2 * 1024 * 1024)
++
++typedef struct mantisbt_custom_fields
++{
++    char *cf_abrt_hash_id;
++    char *cf_url_id;
++} mantisbt_custom_fields_t;
++
++/*
++ * MantisBT settings issue info
++ */
++void
++mantisbt_settings_free(mantisbt_settings_t *s)
++{
++    if (s == NULL)
++        return;
++
++    free(s->m_login);
++    free(s->m_password);
++    free(s->m_project);
++    free(s->m_project_id);
++    free(s->m_project_version);
++}
++
++/*
++ * MantisBT issue info
++ */
++mantisbt_issue_info_t *
++mantisbt_issue_info_new()
++{
++    mantisbt_issue_info_t *info = xzalloc(sizeof(mantisbt_issue_info_t));
++    info->mii_id = -1;
++    info->mii_dup_id = -1;
++
++    return info;
++}
++
++void
++mantisbt_issue_info_free(mantisbt_issue_info_t *info)
++{
++    if (info == NULL)
++        return;
++
++    free(info->mii_status);
++    free(info->mii_resolution);
++    free(info->mii_reporter);
++    free(info->mii_project);
++
++    list_free_with_free(info->mii_notes);
++    list_free_with_free(info->mii_attachments);
++
++    free(info);
++}
++
++mantisbt_issue_info_t *
++mantisbt_find_origin_bug_closed_duplicate(mantisbt_settings_t *settings, mantisbt_issue_info_t *info)
++{
++    mantisbt_issue_info_t *info_tmp = mantisbt_issue_info_new();
++    info_tmp->mii_id = info->mii_id;
++    info_tmp->mii_dup_id = info->mii_dup_id;
++
++    for (int ii = 0; ii <= MAX_HOPS; ii++)
++    {
++        if (ii == MAX_HOPS)
++            error_msg_and_die(_("MantisBT couldn't find parent of issue %d"), info->mii_id);
++
++        log("Issue %d is a duplicate, using parent issue %d", info_tmp->mii_id, info_tmp->mii_dup_id);
++        int issue_id = info_tmp->mii_dup_id;
++
++        mantisbt_issue_info_free(info_tmp);
++        info_tmp = mantisbt_get_issue_info(settings, issue_id);
++
++        // found a issue which is not CLOSED as DUPLICATE
++        if (info_tmp->mii_dup_id == -1)
++            break;
++    }
++
++    return info_tmp;
++}
++
++/*
++ * SOAP request
++ */
++static soap_request_t *
++soap_request_new()
++{
++    soap_request_t *req = xzalloc(sizeof(*req));
++
++    return req;
++}
++
++void
++soap_request_free(soap_request_t *req)
++{
++    if (req == NULL)
++        return;
++
++    if (req->sr_root != NULL)
++        xmlFreeDoc(req->sr_root->doc);
++
++    free(req);
++
++    return;
++}
++
++static xmlNodePtr
++soap_node_get_next_element_node(xmlNodePtr node)
++{
++    for (; node != NULL; node = node->next)
++        if (node->type == XML_ELEMENT_NODE)
++            break;
++
++    return node;
++}
++
++static xmlNodePtr
++soap_node_get_child_element(xmlNodePtr node)
++{
++    if (node == NULL)
++        error_msg_and_die(_("SOAP: Failed to get child element because of no parent."));
++
++    return soap_node_get_next_element_node(node->xmlChildrenNode);
++}
++
++static xmlNodePtr
++soap_node_get_next_sibling(xmlNodePtr node)
++{
++    if (node == NULL)
++        error_msg_and_die(_("SOAP: Failed to get next element because of no node."));
++
++    return soap_node_get_next_element_node(node->next);
++}
++
++static xmlNodePtr
++soap_node_get_child_node(xmlNodePtr parent, const char *name)
++{
++    if (parent == NULL)
++        error_msg_and_die(_("SOAP: Failed to get child node because of no parent."));
++
++    xmlNodePtr node;
++    for (node = soap_node_get_child_element(parent); node != NULL; node = soap_node_get_next_sibling(node))
++    {
++        if (xmlStrcmp(node->name, BAD_CAST name) == 0)
++            return node;
++    }
++
++    return NULL;
++}
++
++soap_request_t *
++soap_request_new_for_method(const char *method)
++{
++    char *xml_str = xasprintf(SOAP_TEMPLATE, method, method);
++
++    xmlDocPtr doc = xmlParseDoc(BAD_CAST xml_str);
++    free(xml_str);
++
++    if (doc == NULL)
++        error_msg_and_die(_("SOAP: Failed to parse xml during creating request."));
++
++    soap_request_t *req = soap_request_new();
++
++    req->sr_root = xmlDocGetRootElement(doc);
++    if (req->sr_root == NULL)
++    {
++        soap_request_free(req);
++        error_msg_and_die(_("SOAP: Failed to get xml root element."));
++    }
++
++    req->sr_body = soap_node_get_child_node(req->sr_root, "Body");
++    req->sr_method = soap_node_get_child_node(req->sr_body, method);
++
++    return req;
++}
++
++static xmlNodePtr
++soap_node_add_child_node(xmlNodePtr node, const char *name, const char *type, const char *value)
++{
++    if (node == NULL || name == NULL)
++        error_msg_and_die(_("SOAP: Failed to add a new child node because of no node or no child name."));
++
++    xmlNodePtr new_node = xmlNewTextChild(node, /* namespace */ NULL, BAD_CAST name, BAD_CAST value);
++
++    if (new_node == NULL)
++        error_msg_and_die(_("SOAP: Failed to create a new xml child item."));
++
++    if (type != NULL)
++    {
++        if (xmlNewProp(new_node, BAD_CAST "xsi:type", BAD_CAST type) == NULL)
++            error_msg_and_die(_("SOAP: Failed to create a new property."));
++    }
++
++    return new_node;
++}
++
++void
++soap_request_add_method_parameter(soap_request_t *req, const char *name, const char *type, const char *value)
++{
++    if (req == NULL || req->sr_method == NULL)
++        error_msg_and_die(_("SOAP: Failed to add method parametr."));
++
++    soap_node_add_child_node(req->sr_method, name, type, value);
++    return;
++}
++
++void
++soap_request_add_credentials_parameter(soap_request_t *req, const mantisbt_settings_t *settings)
++{
++    soap_request_add_method_parameter(req, "username", SOAP_STRING, settings->m_login);
++    soap_request_add_method_parameter(req, "password", SOAP_STRING, settings->m_password);
++
++    return;
++}
++
++static void
++soap_add_new_issue_parameters(soap_request_t *req,
++                               const char *project,
++                               const char *version,
++                               const char *category,
++                               const char *summary,
++                               const char *description,
++                               const char *additional_information,
++                               bool private,
++                               mantisbt_custom_fields_t *fields,
++                               const char *duphash,
++                               const char *tracker_url)
++{
++    if (req == NULL || req->sr_method == NULL)
++        error_msg_and_die(_("SOAP: Failed to add new issue parametrs."));
++
++    if (project == NULL || category == NULL || summary == NULL || description == NULL)
++        error_msg_and_die(_("SOAP: Failed to add new issue parameters because the required items are missing."));
++
++    xmlNodePtr issue_node = soap_node_add_child_node(req->sr_method, "issue", SOAP_ISSUEDATA, /* content */ NULL);
++
++    // project
++    xmlNodePtr project_node = soap_node_add_child_node(issue_node, "project", SOAP_OBJECTREF, /* content */ NULL);
++    soap_node_add_child_node(project_node, "name", SOAP_STRING, project);
++
++    // view status
++    xmlNodePtr view_node = soap_node_add_child_node(issue_node, "view_state", SOAP_OBJECTREF, /* content */ NULL);
++    soap_node_add_child_node(view_node, "name", SOAP_STRING, (private) ? "private" : "public");
++
++    /* if any custom field exists */
++    int custom_fields_count = 0;
++    xmlNodePtr duphash_node;
++    if (fields->cf_abrt_hash_id != NULL || fields->cf_url_id != NULL)
++        duphash_node = soap_node_add_child_node(issue_node, "custom_fields", SOAP_CUSTOMFIELD_ARRAY, /* content */ NULL);
++
++    // custom fields (duphash and URL to tracker)
++    xmlNodePtr item_node, field_node;
++    if (fields->cf_abrt_hash_id != NULL)
++    {
++        item_node = soap_node_add_child_node(duphash_node, "item", SOAP_CUSTOMFIELD, /* content */ NULL);
++        field_node = soap_node_add_child_node(item_node, "field", SOAP_OBJECTREF, /* content */ NULL);
++        soap_node_add_child_node(field_node, "id", SOAP_INTEGER, /* custom_field id */ fields->cf_abrt_hash_id);
++        soap_node_add_child_node(item_node, "value", SOAP_STRING, duphash);
++        ++custom_fields_count;
++    }
++
++    // if tracker url exists, attach it to the issue
++    if (tracker_url != NULL && fields->cf_url_id != NULL)
++    {
++        item_node = soap_node_add_child_node(duphash_node, "item", SOAP_CUSTOMFIELD, /* content */ NULL);
++        field_node = soap_node_add_child_node(item_node, "field", SOAP_OBJECTREF, /* content */ NULL);
++        soap_node_add_child_node(field_node, "id", SOAP_INTEGER, /* custom_field */ fields->cf_url_id);
++        soap_node_add_child_node(item_node, "value", SOAP_STRING, tracker_url);
++        ++custom_fields_count;
++    }
++
++    if (custom_fields_count > 0)
++    {
++        char *type = xasprintf("%s[%i]", SOAP_CUSTOMFIELD, custom_fields_count);
++
++        if (xmlNewProp(duphash_node, BAD_CAST "ns3:arrayType", BAD_CAST type) == NULL)
++            error_msg_and_die(_("SOAP: Failed to create a new property in custom fields."));
++
++        free(type);
++    }
++
++    soap_node_add_child_node(issue_node, "os_build", SOAP_STRING, version);
++    soap_node_add_child_node(issue_node, "category", SOAP_STRING, category);
++    soap_node_add_child_node(issue_node, "summary", SOAP_STRING, summary);
++    soap_node_add_child_node(issue_node, "description", SOAP_STRING, description);
++    soap_node_add_child_node(issue_node, "additional_information", SOAP_STRING, additional_information);
++
++    return;
++}
++
++char *
++soap_request_to_str(const soap_request_t *req)
++{
++    if (req == NULL || req->sr_root == NULL || req->sr_root->doc == NULL)
++        error_msg_and_die(_("SOAP: Failed to create SOAP string because of invalid function arguments."));
++
++    xmlBufferPtr buffer = xmlBufferCreate();
++    int err = xmlNodeDump(buffer, req->sr_root->doc, req->sr_root, 1, /* formatting */ 0);
++    if (err == -1)
++    {
++        xmlBufferFree(buffer);
++        error_msg_and_die(_("SOAP: Failed to dump xml node."));
++    }
++
++    char *ret = xasprintf("%s%s", XML_VERSION, (const char *) xmlBufferContent(buffer));
++    xmlBufferFree(buffer);
++
++    return ret;
++}
++
++#if 0
++void
++soap_request_print(soap_request_t *req)
++{
++    if (req == NULL || req->sr_root == NULL || req->sr_root->doc == NULL)
++        error_msg_and_die(_("SOAP: Failed to print SOAP string."));
++
++    xmlBufferPtr buffer = xmlBufferCreate();
++    int err = xmlNodeDump(buffer, req->sr_root->doc, req->sr_root, 1, /* formatting */ 0);
++    if (err == -1)
++    {
++        xmlBufferFree(buffer);
++        error_msg_and_die(_("Failed to dump xml node."));
++    }
++
++    puts((const char *) xmlBufferContent(buffer));
++
++    xmlBufferFree(buffer);
++    return;
++}
++#endif
++
++static bool
++reader_move_reader_if_node_type_is_element_with_name_and_verify_its_value(xmlTextReaderPtr reader, const char *name)
++{
++    /* is not element node */
++    if (xmlTextReaderNodeType(reader) != XML_ELEMENT_NODE)
++        return false;
++
++    /* is not required name */
++    if (xmlStrcmp(xmlTextReaderConstName(reader), BAD_CAST name) != 0)
++        return false;
++
++    /* read next node */
++    if (xmlTextReaderRead(reader) != 1)
++        return false;
++
++    /* no value node */
++    if (xmlTextReaderHasValue(reader) == 0)
++        return false;
++
++    /* no text node */
++    if (xmlTextReaderNodeType(reader) != XML_TEXT_NODE)
++        return false;
++
++    return true;
++}
++
++static void
++reader_find_element_by_name(xmlTextReaderPtr reader, const char *name)
++{
++    while (xmlTextReaderRead(reader) == 1)
++    {
++        /* is not element node */
++        if (xmlTextReaderNodeType(reader) != XML_ELEMENT_NODE)
++            continue;
++
++        /* is not required name */
++        if (xmlStrcmp(xmlTextReaderConstName(reader), BAD_CAST name) != 0)
++            continue;
++
++        break;
++    }
++
++    return;
++}
++
++/* It is not possible to search only by name because the response contains
++ * different node with the same name. (e.g. id - user id, project id, issue id etc.)
++ * We are interested in only about issues id which is located at a different depth than others.
++ * ...
++ *   <item xsi:type="ns1:IssueData">
++ *      <id xsi:type="xsd:integer">10</id>      <-- This is issue ID (required)
++ *      <view_state xsi:type="ns1:ObjectRef">
++ *          <id xsi:type="xsd:integer">10</id>  <-- This is view_state ID (not required)
++ *          <name xsi:type="xsd:string">public</name>
++ *      </view_state>
++ *      <project xsi:type="ns1:ObjectRef">
++ *          <id xsi:type="xsd:integer">1</id>   <-- This is project ID (not required)
++ *          <name xsi:type="xsd:string">test</name>
++ *      </project>
++ * ...
++ */
++static GList *
++response_values_at_depth_by_name(const char *xml, const char *name, int depth)
++{
++    xmlDocPtr doc = xmlParseDoc(BAD_CAST xml);
++    if (doc == NULL)
++        error_msg_and_die(_("SOAP: Failed to parse xml (searching value at depth by name)."));
++
++    xmlTextReaderPtr reader = xmlReaderWalker(doc);
++    if (reader == NULL)
++        error_msg_and_die(_("SOAP: Failed to create xml text reader."));
++
++    GList *result = NULL;
++
++    const xmlChar *value;
++    while (xmlTextReaderRead(reader) == 1)
++    {
++        /* is not right depth */
++        if (depth != -1 && xmlTextReaderDepth(reader) != depth)
++            continue;
++
++        if (reader_move_reader_if_node_type_is_element_with_name_and_verify_its_value(reader, name) == false)
++            continue;
++
++        if ((value = xmlTextReaderConstValue(reader)) != NULL)
++            result = g_list_append(result, xstrdup((const char *) value));
++    }
++    xmlFreeTextReader(reader);
++
++    return result;
++}
++
++/*
++ * Finds an element named 'elem' and returns a text of a child named 'name'
++ *
++ * Example:
++ *  For
++ *  <elem>
++ *      <id>1</id>
++ *      <name>foo</name>
++ *  </elem>
++ *
++ *  returns "foo"
++ */
++static char *
++response_get_name_value_of_element(const char *xml, const char *element)
++{
++    xmlDocPtr doc = xmlParseDoc(BAD_CAST xml);
++    if (doc == NULL)
++        error_msg_and_die(_("SOAP: Failed to parse xml."));
++
++    xmlTextReaderPtr reader = xmlReaderWalker(doc);
++    if (reader == NULL)
++        error_msg_and_die(_("SOAP: Failed to create xml text reader."));
++
++    const xmlChar *value = NULL;
++
++    reader_find_element_by_name(reader, element);
++
++    /* find 'name' element and return its text */
++    while (xmlTextReaderRead(reader) == 1)
++    {
++        if (reader_move_reader_if_node_type_is_element_with_name_and_verify_its_value(reader, "name") == false)
++            continue;
++
++        if ((value = xmlTextReaderConstValue(reader)) != NULL)
++            break;
++    }
++    xmlFreeTextReader(reader);
++
++    return (char *) value;
++}
++
++static int
++response_get_id_of_relatedto_issue(const char *xml)
++{
++    xmlDocPtr doc = xmlParseDoc(BAD_CAST xml);
++    if (doc == NULL)
++        error_msg_and_die(_("SOAP: Failed to parse xml (get related to issue)."));
++
++    xmlTextReaderPtr reader = xmlReaderWalker(doc);
++    if (reader == NULL)
++        error_msg_and_die(_("SOAP: Failed to create xml text reader."));
++
++    const xmlChar *value = NULL;
++    const xmlChar *id = NULL;
++
++    /* find relationships section */
++    reader_find_element_by_name(reader, "relationships");
++
++    /* find "name" value of 'name' element */
++    while (xmlTextReaderRead(reader) == 1)
++    {
++        /* find type of relattionship */
++        if (reader_move_reader_if_node_type_is_element_with_name_and_verify_its_value(reader, "name") == false)
++            continue;
++
++        if ((value = xmlTextReaderConstValue(reader)) == NULL)
++            continue;
++
++        /* we need 'duplicate of' realtionship type */
++        if (xmlStrcmp(value, BAD_CAST "duplicate of") != 0)
++            continue;
++
++        /* find id of duplicate issues */
++        reader_find_element_by_name(reader, "target_id");
++
++        /* verify target_id node */
++        if (reader_move_reader_if_node_type_is_element_with_name_and_verify_its_value(reader, "target_id") == false)
++            continue;
++
++        /* get its value */
++        if ((id = xmlTextReaderConstValue(reader)) != NULL)
++            break;
++    }
++    xmlFreeTextReader(reader);
++
++    return (id == NULL) ? -1 : atoi((const char *) id);
++}
++
++GList *
++response_get_main_ids_list(const char *xml)
++{
++    return response_values_at_depth_by_name(xml, "id", 5);
++}
++
++int
++response_get_main_id(const char *xml)
++{
++    GList *l = response_values_at_depth_by_name(xml, "id", 5);
++    return (l != NULL) ? atoi(l->data) : -1;
++}
++
++static int
++response_get_return_value(const char *xml)
++{
++    GList *l = response_values_at_depth_by_name(xml, "return", 3);
++    return (l != NULL) ? atoi(l->data) : -1;
++}
++
++static char*
++response_get_return_value_as_string(const char *xml)
++{
++    GList *l = response_values_at_depth_by_name(xml, "return", 3);
++    return (l != NULL) ? l->data : NULL;
++}
++
++static char *
++response_get_error_msg(const char *xml)
++{
++    GList *l = response_values_at_depth_by_name(xml, "faultstring", 3);
++    return (l != NULL) ? xstrdup(l->data) : NULL;
++}
++
++static char *
++response_get_additioanl_information(const char *xml)
++{
++    GList *l = response_values_at_depth_by_name(xml, "additional_information", -1);
++    return (l != NULL) ? xstrdup(l->data) : NULL;
++}
++
++void
++response_values_free(GList *values)
++{
++    g_list_free_full(values, free);
++}
++
++/*
++ * POST
++ */
++
++void
++mantisbt_result_free(mantisbt_result_t *result)
++{
++    if (result == NULL)
++        return;
++
++    free(result->mr_url);
++    free(result->mr_msg);
++    free(result->mr_body);
++    free(result);
++}
++
++mantisbt_result_t *
++mantisbt_soap_call(const mantisbt_settings_t *settings, const soap_request_t *req)
++{
++    char *request = soap_request_to_str(req);
++
++    const char *url = settings->m_mantisbt_soap_url;
++
++    mantisbt_result_t *result = xzalloc(sizeof(*result));
++
++    if (url == NULL || request == NULL)
++    {
++        result->mr_error = -2;
++        result->mr_msg = xasprintf(_("Url or request isn't specified."));
++        free(request);
++
++        return result;
++    }
++
++    char *url_copy = NULL;
++
++    int redirect_count = 0;
++    char *errmsg;
++    post_state_t *post_state;
++
++redirect:
++    post_state = new_post_state(0
++            + POST_WANT_HEADERS
++            + POST_WANT_BODY
++            + POST_WANT_ERROR_MSG
++            + (settings->m_ssl_verify ? POST_WANT_SSL_VERIFY : 0)
++    );
++
++    post_string(post_state, settings->m_mantisbt_soap_url, "text/xml", NULL, request);
++
++    char *location = find_header_in_post_state(post_state, "Location:");
++
++    switch (post_state->http_resp_code)
++    {
++    case 404:
++        result->mr_error = -1;
++        result->mr_msg = xasprintf(_("Error in HTTP POST, "
++                        "HTTP code: 404 (Not found), URL:'%s'"), url);
++        break;
++    case 500:
++        result->mr_error = -1;
++        result->mr_msg = response_get_error_msg(post_state->body);
++
++        break;
++    case 301: /* "301 Moved Permanently" (for example, used to move http:// to https://) */
++    case 302: /* "302 Found" (just in case) */
++    case 305: /* "305 Use Proxy" */
++        if (++redirect_count < 10 && location)
++        {
++            free(url_copy);
++            url = url_copy = xstrdup(location);
++            free_post_state(post_state);
++            goto redirect;
++        }
++        /* fall through */
++
++    default:
++        result->mr_error = -1;
++        errmsg = post_state->curl_error_msg;
++        if (errmsg && errmsg[0])
++            result->mr_msg = xasprintf(_("Error in MantisBT request at '%s': %s"), url, errmsg);
++        else
++            result->mr_msg = xasprintf(_("Error in MantisBT request at '%s'"), url);
++        break;
++
++    case 200:
++    case 201:
++        /* sent successfully */
++        result->mr_url = xstrdup(location); /* note: xstrdup(NULL) returns NULL */
++    } /* switch (HTTP code) */
++
++    result->mr_http_resp_code = post_state->http_resp_code;
++    result->mr_body = post_state->body;
++    post_state->body = NULL;
++
++    free_post_state(post_state);
++    free(url_copy);
++    free(request);
++
++    return result;
++}
++
++int
++mantisbt_attach_data(const mantisbt_settings_t *settings, const char *bug_id,
++                    const char *att_name, const char *data, int size)
++{
++    soap_request_t *req = soap_request_new_for_method("mc_issue_attachment_add");
++    soap_request_add_credentials_parameter(req, settings);
++
++    soap_request_add_method_parameter(req, "issue_id", SOAP_INTEGER, bug_id);
++    soap_request_add_method_parameter(req, "name", SOAP_STRING, att_name);
++
++    soap_request_add_method_parameter(req, "file_type", SOAP_STRING, "text");
++    soap_request_add_method_parameter(req, "content", SOAP_BASE64, encode_base64(data, size));
++
++    mantisbt_result_t *result = mantisbt_soap_call(settings, req);
++    soap_request_free(req);
++
++    if (result->mr_http_resp_code != 200)
++    {
++        int ret = -1;
++        if (strcmp(result->mr_msg, "Duplicate filename.") == 0)
++            ret = -2;
++
++        error_msg(_("Failed to attach file: '%s'"), result->mr_msg);
++        mantisbt_result_free(result);
++        return ret;
++    }
++
++    int id = response_get_return_value(result->mr_body);
++
++    mantisbt_result_free(result);
++
++    return id;
++}
++
++static int
++mantisbt_attach_fd(const mantisbt_settings_t *settings, const char *bug_id,
++                const char *att_name, int fd)
++{
++    off_t size = lseek(fd, 0, SEEK_END);
++    if (size < 0)
++    {
++        perror_msg(_("Can't lseek '%s'"), att_name);
++        return -1;
++    }
++
++    if (size >= MANTISBT_MAX_FILE_UPLOAD_SIZE)
++    {
++        error_msg(_("Can't upload '%s', it's too large (%llu bytes)"), att_name, (long long)size);
++        return -1;
++    }
++    lseek(fd, 0, SEEK_SET);
++
++    char *data = xmalloc(size + 1);
++    ssize_t r = full_read(fd, data, size);
++    if (r < 0)
++    {
++        free(data);
++        perror_msg(_("Can't read '%s'"), att_name);
++        return -1;
++    }
++
++    int res = mantisbt_attach_data(settings, bug_id, att_name, data, size);
++    free(data);
++    return res;
++}
++
++int
++mantisbt_attach_file(const mantisbt_settings_t *settings, const char *bug_id,
++                    const char *att_name, const char *path)
++{
++    int fd = open(path, O_RDONLY);
++    if (fd < 0)
++    {
++        perror_msg(_("Can't open '%s'"), path);
++        return 0;
++    }
++    errno = 0;
++    struct stat st;
++    if (fstat(fd, &st) != 0 || !S_ISREG(st.st_mode))
++    {
++        perror_msg("'%s': not a regular file", path);
++        close(fd);
++        return 0;
++    }
++    log_debug("attaching '%s' as file", att_name);
++    int ret = mantisbt_attach_fd(settings, bug_id, att_name, fd);
++    close(fd);
++    return ret;
++}
++
++static void
++soap_filter_add_new_array_parameter(xmlNodePtr filter_node, const char *name, const char *type, const char *value)
++{
++    const char *array_type = NULL;
++    if( strcmp(type, SOAP_INTEGER) == 0 )
++        array_type = SOAP_INTEGERARRAY;
++    else
++        array_type = SOAP_STRINGARRAY;
++
++    xmlNodePtr filter_item = soap_node_add_child_node(filter_node, name, array_type, /* content */ NULL);
++    soap_node_add_child_node(filter_item, "item", type, value);
++}
++
++static void
++soap_filter_custom_fields_add_new_item(xmlNodePtr filter_node, const char *custom_field_name, const char *value)
++{
++    xmlNodePtr item_node = soap_node_add_child_node(filter_node, "item", SOAP_FILTER_CUSTOMFIELD, /* content */ NULL);
++
++    xmlNodePtr field_node = soap_node_add_child_node(item_node, "field", SOAP_OBJECTREF, /* content */ NULL);
++    soap_node_add_child_node(field_node, "name", SOAP_STRING, custom_field_name);
++
++    xmlNodePtr value_node = soap_node_add_child_node(item_node, "value", SOAP_STRINGARRAY, /* content */ NULL);
++    soap_node_add_child_node(value_node, "item", SOAP_STRING, value);
++}
++
++GList *
++mantisbt_search_by_abrt_hash(mantisbt_settings_t *settings, const char *abrt_hash)
++{
++    soap_request_t *req = soap_request_new_for_method("mc_filter_search_issues");
++    soap_request_add_credentials_parameter(req, settings);
++
++    xmlNodePtr filter_node = soap_node_add_child_node(req->sr_method, "filter", SOAP_FILTER_SEARCH_DATA, /* content */ NULL);
++
++    /* 'hide_status_is : -2' means, searching within all status */
++    soap_filter_add_new_array_parameter(filter_node, "hide_status_id", SOAP_INTEGERARRAY, "-2");
++
++    // custom fields
++    xmlNodePtr custom_fields_node = soap_node_add_child_node(filter_node, "custom_fields", SOAP_FILTER_CUSTOMFIELD_ARRAY, /* content */ NULL);
++
++    // custom field 'abrt_hash'
++    soap_filter_custom_fields_add_new_item(custom_fields_node, "abrt_hash", abrt_hash);
++
++    soap_request_add_method_parameter(req, "page_number", SOAP_INTEGER, "1");
++    soap_request_add_method_parameter(req, "per_page", SOAP_INTEGER, /* -1 means get all issues */ "-1");
++
++    mantisbt_result_t *result = mantisbt_soap_call(settings, req);
++    soap_request_free(req);
++
++    if (result->mr_error == -1)
++    {
++        error_msg(_("Failed to search MantisBT issue by duphash: '%s'"), result->mr_msg);
++        mantisbt_result_free(result);
++        return NULL;
++    }
++
++    GList *ids = response_get_main_ids_list(result->mr_body);
++
++    return ids;
++}
++
++GList *
++mantisbt_search_duplicate_issues(mantisbt_settings_t *settings, const char *category,
++                                     const char *version, const char *abrt_hash)
++{
++    soap_request_t *req = soap_request_new_for_method("mc_filter_search_issues");
++    soap_request_add_credentials_parameter(req, settings);
++
++    xmlNodePtr filter_node = soap_node_add_child_node(req->sr_method, "filter", SOAP_FILTER_SEARCH_DATA, /* content */ NULL);
++
++    soap_filter_add_new_array_parameter(filter_node, "project_id", SOAP_INTEGERARRAY, settings->m_project_id);
++
++    /* 'hide_status_is : -2' means, searching within all status */
++    soap_filter_add_new_array_parameter(filter_node, "hide_status_id", SOAP_INTEGERARRAY, "-2");
++
++    soap_filter_add_new_array_parameter(filter_node, "category", SOAP_STRINGARRAY, category);
++
++    // custom fields
++    xmlNodePtr custom_fields_node = soap_node_add_child_node(filter_node, "custom_fields", SOAP_FILTER_CUSTOMFIELD_ARRAY, /* content */ NULL);
++
++    // custom field 'abrt_hash'
++    soap_filter_custom_fields_add_new_item(custom_fields_node, "abrt_hash", abrt_hash);
++
++    // version
++    if (version != NULL)
++        soap_filter_add_new_array_parameter(filter_node, "os_build", SOAP_STRINGARRAY, version);
++
++    soap_request_add_method_parameter(req, "page_number", SOAP_INTEGER, "1");
++    soap_request_add_method_parameter(req, "per_page", SOAP_INTEGER, /* -1 means get all issues */ "-1");
++
++    mantisbt_result_t *result = mantisbt_soap_call(settings, req);
++    soap_request_free(req);
++
++    if (result->mr_error == -1)
++    {
++        error_msg(_("Failed to search MantisBT duplicate issue: '%s'"), result->mr_msg);
++        mantisbt_result_free(result);
++        return NULL;
++    }
++
++    GList *ids = response_get_main_ids_list(result->mr_body);
++
++    return ids;
++}
++
++static char *
++custom_field_get_id_from_name(GList *ids, GList *names, const char *name)
++{
++    GList *i = ids;
++    GList *n = names;
++    for (; i != NULL; i = i->next, n = n->next)
++    {
++        if (strcmp(n->data, name) == 0)
++            return i->data;
++    }
++
++    return NULL;
++}
++
++static void
++custom_field_ask(const char *name)
++{
++    char *msg = xasprintf(_("CentOS Bug Tracker doesn't contain custom field '%s', which is required for full functionality of the reporter. Do you still want to create a new issue?"), name);
++    int yes = ask_yes_no(msg);
++    free(msg);
++
++    if (!yes)
++    {
++        set_xfunc_error_retval(EXIT_CANCEL_BY_USER);
++        xfunc_die();
++    }
++
++    return;
++}
++
++static void
++mantisbt_get_custom_fields(const mantisbt_settings_t *settings, mantisbt_custom_fields_t *fields, const char *project_id)
++{
++    soap_request_t *req = soap_request_new_for_method("mc_project_get_custom_fields");
++    soap_request_add_credentials_parameter(req, settings);
++    soap_request_add_method_parameter(req, "project_id", SOAP_INTEGER, project_id);
++
++    mantisbt_result_t *result = mantisbt_soap_call(settings, req);
++    soap_request_free(req);
++
++    if (result->mr_http_resp_code != 200)
++        error_msg_and_die(_("Failed to get custom fields for '%s' project"), settings->m_project);
++
++    GList *ids = response_values_at_depth_by_name(result->mr_body, "id", -1);
++    GList *names = response_values_at_depth_by_name(result->mr_body, "name", -1);
++
++    mantisbt_result_free(result);
++
++    if ((fields->cf_abrt_hash_id = custom_field_get_id_from_name(ids, names, CUSTOMFIELD_DUPHASH)) == NULL)
++        custom_field_ask(CUSTOMFIELD_DUPHASH);
++
++    if ((fields->cf_url_id = custom_field_get_id_from_name(ids, names, CUSTOMFIELD_URL)) == NULL)
++        custom_field_ask(CUSTOMFIELD_URL);
++
++    return;
++}
++
++int
++mantisbt_create_new_issue(const mantisbt_settings_t *settings,
++                   problem_data_t *problem_data,
++                   const problem_report_t *pr,
++                   const char *tracker_url)
++{
++
++    const char *category = problem_data_get_content_or_NULL(problem_data, FILENAME_COMPONENT);
++    const char *duphash = problem_data_get_content_or_NULL(problem_data, FILENAME_DUPHASH);
++
++    char *summary = shorten_string_to_length(problem_report_get_summary(pr), MAX_SUMMARY_LENGTH);
++
++    const char *description = problem_report_get_description(pr);
++    const char *additional_information = problem_report_get_section(pr, PR_SEC_ADDITIONAL_INFO);
++
++    mantisbt_custom_fields_t fields;
++    mantisbt_get_custom_fields(settings, &fields, settings->m_project_id);
++
++    soap_request_t *req = soap_request_new_for_method("mc_issue_add");
++    soap_request_add_credentials_parameter(req, settings);
++    soap_add_new_issue_parameters(req, settings->m_project, settings->m_project_version, category, summary, description, additional_information, settings->m_create_private, &fields, duphash, tracker_url);
++
++    mantisbt_result_t *result = mantisbt_soap_call(settings, req);
++    soap_request_free(req);
++    free(summary);
++
++    if (result->mr_error == -1)
++    {
++        error_msg(_("Failed to create a new issue: '%s'"), result->mr_msg);
++        mantisbt_result_free(result);
++        return -1;
++    }
++
++    int id = response_get_return_value(result->mr_body);
++
++    mantisbt_result_free(result);
++    return id;
++}
++
++mantisbt_issue_info_t *
++mantisbt_get_issue_info(const mantisbt_settings_t *settings, int issue_id)
++{
++    soap_request_t *req = soap_request_new_for_method("mc_issue_get");
++    soap_request_add_credentials_parameter(req, settings);
++
++    char *issue_id_str = xasprintf("%d", issue_id);
++    soap_request_add_method_parameter(req, "issue_id", SOAP_INTEGER, issue_id_str);
++    free(issue_id_str);
++
++    mantisbt_result_t *result = mantisbt_soap_call(settings, req);
++    soap_request_free(req);
++
++    if (result->mr_error == -1)
++    {
++        error_msg(_("Failed to get MantisBT issue: '%s'"), result->mr_msg);
++        mantisbt_result_free(result);
++        return NULL;
++    }
++
++    mantisbt_issue_info_t *issue_info = mantisbt_issue_info_new();
++
++    issue_info->mii_id = issue_id;
++    issue_info->mii_status = response_get_name_value_of_element(result->mr_body, "status");
++    issue_info->mii_resolution = response_get_name_value_of_element(result->mr_body, "resolution");
++    issue_info->mii_reporter = response_get_name_value_of_element(result->mr_body, "reporter");
++    issue_info->mii_project = response_get_name_value_of_element(result->mr_body, "project");
++
++    if (strcmp(issue_info->mii_status, "closed") == 0 && !issue_info->mii_resolution)
++        error_msg(_("Issue %i is CLOSED, but it has no RESOLUTION"), issue_info->mii_id);
++
++    issue_info->mii_dup_id = response_get_id_of_relatedto_issue(result->mr_body);
++
++    if (strcmp(issue_info->mii_status, "closed") == 0
++        && strcmp(issue_info->mii_resolution, "duplicate") == 0
++        && issue_info->mii_dup_id == -1 )
++    {
++        error_msg(_("Issue %i is CLOSED as DUPLICATE, but it has no DUPLICATE_ID"),
++                            issue_info->mii_id);
++    }
++
++    /* notes are stored in <text> element */
++    issue_info->mii_notes = response_values_at_depth_by_name(result->mr_body, "text", -1);
++
++    /* looking for bt rating in additional information too */
++    char *add_info = response_get_additioanl_information(result->mr_body);
++    if (add_info != NULL)
++        issue_info->mii_notes = g_list_append (issue_info->mii_notes, add_info);
++    issue_info->mii_attachments = response_values_at_depth_by_name(result->mr_body, "filename", -1);
++    issue_info->mii_best_bt_rating = comments_find_best_bt_rating(issue_info->mii_notes);
++
++    mantisbt_result_free(result);
++    return issue_info;
++}
++
++int
++mantisbt_add_issue_note(const mantisbt_settings_t *settings, int issue_id, const char *note)
++{
++    soap_request_t *req = soap_request_new_for_method("mc_issue_note_add");
++    soap_request_add_credentials_parameter(req, settings);
++
++    char *issue_id_str = xasprintf("%i", issue_id);
++    soap_node_add_child_node(req->sr_method, "issue_id", SOAP_INTEGER, issue_id_str);
++
++    xmlNodePtr note_node = soap_node_add_child_node(req->sr_method, "note", SOAP_ISSUENOTE, /* content */ NULL);
++    soap_node_add_child_node(note_node, "text", SOAP_STRING, note);
++
++    mantisbt_result_t *result = mantisbt_soap_call(settings, req);
++
++    free(issue_id_str);
++    soap_request_free(req);
++
++    if (result->mr_error == -1)
++    {
++        error_msg(_("Failed to add MantisBT issue note: '%s'"), result->mr_msg);
++        mantisbt_result_free(result);
++        return -1;
++    }
++    int id = response_get_return_value(result->mr_body);
++
++    mantisbt_result_free(result);
++    return id;
++}
++
++void
++mantisbt_get_project_id_from_name(mantisbt_settings_t *settings)
++{
++    if (settings->m_project == NULL)
++        error_msg_and_die(_("The MantisBT project has not been deretmined."));
++
++    soap_request_t *req = soap_request_new_for_method("mc_project_get_id_from_name");
++    soap_request_add_credentials_parameter(req, settings);
++    soap_node_add_child_node(req->sr_method, "project_name", SOAP_STRING, settings->m_project);
++
++    mantisbt_result_t *result = mantisbt_soap_call(settings, req);
++
++    if (result->mr_http_resp_code != 200)
++        error_msg_and_die(_("Failed to get project id from name"));
++
++    settings->m_project_id = response_get_return_value_as_string(result->mr_body);
++
++    return;
++}
+diff --git a/src/plugins/mantisbt.conf b/src/plugins/mantisbt.conf
+new file mode 100644
+index 0000000..15a1065
+--- /dev/null
++++ b/src/plugins/mantisbt.conf
+@@ -0,0 +1,8 @@
++# MantisBT URL
++MantisbtURL = https://bugs.centos.org/
++# yes means that ssl certificates will be checked
++SSLVerify = yes
++# your login has to exist, if you don have any, please create one
++Login =
++# your password
++Password =
+diff --git a/src/plugins/mantisbt.h b/src/plugins/mantisbt.h
+new file mode 100644
+index 0000000..c31c174
+--- /dev/null
++++ b/src/plugins/mantisbt.h
+@@ -0,0 +1,140 @@
++/*
++    Copyright (C) 2014  ABRT team
++    Copyright (C) 2014  RedHat Inc
++
++    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 2 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, write to the Free Software Foundation, Inc.,
++    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
++*/
++
++#ifndef MANTISBT_H
++#define MANTISBT_H
++
++#ifdef __cplusplus
++extern "C" {
++#endif
++
++#include <libxml/encoding.h>
++#include "problem_report.h"
++
++#define SOAP_STRING "ns2:string"
++#define SOAP_INTEGER "ns2:integer"
++#define SOAP_INTEGERARRAY "ns2:IntegerArray"
++#define SOAP_STRINGARRAY "ns2:StringArray"
++#define SOAP_ISSUEDATA "ns3:IssueData"
++#define SOAP_OBJECTREF "ns3:ObjectRef"
++#define SOAP_CUSTOMFIELD_ARRAY "ns2:CustomFieldValueForIssueDataArray"
++#define SOAP_FILTER_CUSTOMFIELD "ns2:FilterCustomField"
++#define SOAP_FILTER_CUSTOMFIELD_ARRAY "ns2:FilterCustomFieldArray"
++#define SOAP_FILTER_SEARCH_DATA "ns2:FilterSearchData"
++#define SOAP_CUSTOMFIELD "ns2:CustomFieldValueForIssueData"
++#define SOAP_BASE64 "SOAP-ENC:base64"
++#define SOAP_ISSUENOTE "ns3:IssueNoteData"
++
++#define PR_SEC_ADDITIONAL_INFO "Additional info"
++
++typedef struct soap_request
++{
++    xmlNodePtr sr_root;
++    xmlNodePtr sr_body;
++    xmlNodePtr sr_method;
++} soap_request_t;
++
++typedef struct mantisbt_settings
++{
++    char *m_login;
++    char *m_password;
++    const char *m_mantisbt_url;
++    const char *m_mantisbt_soap_url;
++    char *m_project;
++    char *m_project_id;
++    char *m_project_version;
++    const char *m_DontMatchComponents;
++    int         m_ssl_verify;
++    int         m_create_private;
++} mantisbt_settings_t;
++
++typedef struct mantisbt_result
++{
++    int mr_http_resp_code;
++    int mr_error;
++    char *mr_msg;
++    char *mr_url;
++    char *mr_body;
++} mantisbt_result_t;
++
++typedef struct mantisbt_issue_info
++{
++    int mii_id;
++    int mii_dup_id;
++    unsigned mii_best_bt_rating;
++
++    char *mii_status;
++    char *mii_resolution;
++    char *mii_reporter;
++    char *mii_project;
++
++    GList *mii_notes;
++    GList *mii_attachments;
++} mantisbt_issue_info_t;
++
++void mantisbt_settings_free(mantisbt_settings_t *settings);
++
++mantisbt_issue_info_t * mantisbt_issue_info_new();
++void mantisbt_issue_info_free(mantisbt_issue_info_t *info);
++mantisbt_issue_info_t * mantisbt_find_origin_bug_closed_duplicate(mantisbt_settings_t *settings, mantisbt_issue_info_t *info);
++
++void soap_request_free(soap_request_t *req);
++
++soap_request_t *soap_request_new_for_method(const char *method);
++
++void soap_request_add_method_parameter(soap_request_t *req, const char *name, const char *type, const char *value);
++void soap_request_add_credentials_parameter(soap_request_t *req, const mantisbt_settings_t *settings);
++
++char *soap_request_to_str(const soap_request_t *req);
++
++#if 0
++void soap_request_print(soap_request_t *req);
++#endif
++
++GList * response_get_main_ids_list(const char *xml);
++int response_get_main_id(const char *xml);
++void response_values_free(GList *values);
++
++void mantisbt_result_free(mantisbt_result_t *result);
++mantisbt_result_t *mantisbt_soap_call(const mantisbt_settings_t *settings, const soap_request_t *req);
++
++int mantisbt_attach_data(const mantisbt_settings_t *settings, const char *bug_id,
++                         const char *att_name, const char *data, int size);
++
++int mantisbt_attach_file(const mantisbt_settings_t *settings, const char *bug_id,
++                    const char *att_name, const char *data);
++
++GList * mantisbt_search_by_abrt_hash(mantisbt_settings_t *settings, const char *abrt_hash);
++GList * mantisbt_search_duplicate_issues(mantisbt_settings_t *settings, const char *category,
++                    const char *version, const char *abrt_hash);
++
++int mantisbt_create_new_issue(const mantisbt_settings_t *settings, problem_data_t *problem_data,
++                       const problem_report_t *pr, const char *tracker_url);
++
++mantisbt_issue_info_t * mantisbt_get_issue_info(const mantisbt_settings_t *settings, int issue_id);
++int mantisbt_add_issue_note(const mantisbt_settings_t *settings, int issue_id, const char *note);
++
++void mantisbt_get_project_id_from_name(mantisbt_settings_t *settings);
++
++#ifdef __cplusplus
++}
++#endif
++
++#endif
++
+diff --git a/src/plugins/mantisbt_format.conf b/src/plugins/mantisbt_format.conf
+new file mode 100644
+index 0000000..da08aa6
+--- /dev/null
++++ b/src/plugins/mantisbt_format.conf
+@@ -0,0 +1,59 @@
++# Lines starting with # are ignored.
++# Lines can be continued on the next line using trailing backslash.
++#
++# Format:
++# %summary:: summary format
++# section:: element1[,element2]...
++# The literal text line to be added to Bugzilla comment. Can be empty.
++# (IOW: empty lines are NOT ignored!)
++#
++# Summary format is a line of text, where %element% is replaced by
++# text element's content, and [[...%element%...]] block is used only if
++# %element% exists. [[...]] blocks can nest.
++#
++# Sections can be:
++# - %summary: bug summary format string.
++# - %attach: a list of elements to attach.
++# - text, double colon (::) and the list of comma-separated elements.
++#   Text can be empty (":: elem1, elem2, elem3" works),
++#   in this case "Text:" header line will be omitted.
++#
++# Elements can be:
++# - problem directory element names, which get formatted as
++#   <element_name>: <contents>
++#   or
++#   <element_name>:
++#   :<contents>
++#   :<contents>
++#   :<contents>
++# - problem directory element names prefixed by "%bare_",
++#   which is formatted as-is, without "<element_name>:" and colons
++# - %oneline, %multiline, %text wildcards, which select all corresponding
++#   elements for output or attachment
++# - %binary wildcard, valid only for %attach section, instructs to attach
++#   binary elements
++# - problem directory element names prefixed by "-",
++#   which excludes given element from all wildcards
++#
++#   Nonexistent elements are silently ignored.
++#   If none of elements exists, the section will not be created.
++
++%summary:: [abrt] %pkg_name%[[: %crash_function%()]][[: %reason%]][[: TAINTED %tainted_short%]]
++
++Description of problem:: %bare_comment
++
++Version-Release number of selected component:: %bare_package
++
++Truncated backtrace:: %bare_%short_backtrace
++
++%Additional info::
++:: -pkg_arch,-pkg_epoch,-pkg_name,-pkg_release,-pkg_version,\
++		-component,-architecture,\
++	-analyzer,-count,-duphash,-uuid,-abrt_version,\
++	-username,-hostname,-os_release,-os_info,\
++	-time,-pid,-pwd,-last_occurrence,-ureports_counter,\
++	%reporter,\
++	%oneline
++
++%attach:: -comment,-reason,-reported_to,-event_log,%multiline,\
++	-coredump,%binary
+diff --git a/src/plugins/mantisbt_formatdup.conf b/src/plugins/mantisbt_formatdup.conf
+new file mode 100644
+index 0000000..b1552ac
+--- /dev/null
++++ b/src/plugins/mantisbt_formatdup.conf
+@@ -0,0 +1,65 @@
++# Lines starting with # are ignored.
++# Lines can be continued on the next line using trailing backslash.
++#
++# Format:
++# %summary:: summary format
++# section:: element1[,element2]...
++# The literal text line to be added to Bugzilla comment. Can be empty.
++# (IOW: empty lines are NOT ignored!)
++#
++# Summary format is a line of text, where %element% is replaced by
++# text element's content, and [[...%element%...]] block is used only if
++# %element% exists. [[...]] blocks can nest.
++#
++# Sections can be:
++# - %summary: bug summary format string.
++# - %attach: a list of elements to attach.
++# - text, double colon (::) and the list of comma-separated elements.
++#   Text can be empty (":: elem1, elem2, elem3" works),
++#   in this case "Text:" header line will be omitted.
++#
++# Elements can be:
++# - problem directory element names, which get formatted as
++#   <element_name>: <contents>
++#   or
++#   <element_name>:
++#   :<contents>
++#   :<contents>
++#   :<contents>
++# - problem directory element names prefixed by "%bare_",
++#   which is formatted as-is, without "<element_name>:" and colons
++# - %oneline, %multiline, %text wildcards, which select all corresponding
++#   elements for output or attachment
++# - %binary wildcard, valid only for %attach section, instructs to attach
++#   binary elements
++# - problem directory element names prefixed by "-",
++#   which excludes given element from all wildcards
++#
++#   Nonexistent elements are silently ignored.
++#   If none of elements exists, the section will not be created.
++
++# When we add a comment to an existing BZ, %summary is ignored
++# (it specifies *new bug* summary field):
++# %summary:: blah blah
++
++# When dup is detected, BZ reporter adds a comment to it.
++# This comment may interrupt an ongoing conversation in the BZ.
++# (Three people independently filed a bug against abrt about this).
++# Need to clearly explain what this comment is, to prevent confusion.
++# Hopefully, this line would suffice:
++Another user experienced a similar problem:
++
++# If user filled out comment field, show it:
++:: %bare_comment
++
++# var_log_messages has too much variance (time/date),
++# we exclude it from message so that dup message elimination has more chances to work
++:: \
++	-pkg_arch,-pkg_epoch,-pkg_name,-pkg_release,-pkg_version,\
++		-component,-architecture,\
++	-analyzer,-count,-duphash,-uuid,-abrt_version,\
++	-username,-hostname,-os_release,-os_info,\
++	-time,-pid,-pwd,-last_occurrence,-ureports_counter,\
++	-var_log_messages,\
++	%reporter,\
++	%oneline
+diff --git a/src/plugins/report_CentOSBugTracker.conf b/src/plugins/report_CentOSBugTracker.conf
+new file mode 100644
+index 0000000..04cab13
+--- /dev/null
++++ b/src/plugins/report_CentOSBugTracker.conf
+@@ -0,0 +1,4 @@
++Mantisbt_MantisbtURL = https://bugs.centos.org
++Mantisbt_Login =
++Mantisbt_Password
++Mantisbt_SSLVerify = yes
+diff --git a/src/plugins/report_CentOSBugTracker.xml.in b/src/plugins/report_CentOSBugTracker.xml.in
+new file mode 100644
+index 0000000..81f909b
+--- /dev/null
++++ b/src/plugins/report_CentOSBugTracker.xml.in
+@@ -0,0 +1,65 @@
++<?xml version="1.0" encoding="UTF-8" ?>
++<event>
++    <_name>CentOS Bug Tracker</_name>
++    <_description>Report to CentOS Bug Tracker</_description>
++
++    <requires-items>component,duphash,os_release</requires-items>
++    <exclude-items-by-default>coredump,count,event_log,reported_to,vmcore</exclude-items-by-default>
++    <exclude-items-always></exclude-items-always>
++    <exclude-binary-items>no</exclude-binary-items>
++    <include-items-by-default></include-items-by-default>
++    <minimal-rating>3</minimal-rating>
++    <gui-review-elements>yes</gui-review-elements>
++
++    <options>
++        <option type="text" name="Mantisbt_Login">
++            <_label>User name</_label>
++            <allow-empty>no</allow-empty>
++            <_description>CentOS Bug Tracker account user name</_description>
++            <_note-html>You can create bugs.centos.org account &lt;a href="https://bugs.centos.org/signup_page.php"&gt;here&lt;/a&gt;</_note-html>
++        </option>
++        <option type="password" name="Mantisbt_Password">
++            <_label>Password</_label>
++            <allow-empty>no</allow-empty>
++            <_description>CentOS Bug Tracker account password</_description>
++        </option>
++        <advanced-options>
++            <option type="text" name="Mantisbt_MantisbtURL">
++                <_label>CentOS Bug Tracker URL</_label>
++                <allow-empty>no</allow-empty>
++                <_note-html>Address of CentOS Bug Tracker server</_note-html>
++                <default-value>https://bugs.centos.org</default-value>
++            </option>
++            <option type="bool" name="Mantisbt_SSLVerify">
++                <_label>Verify SSL</_label>
++                <_note-html>Check SSL key validity</_note-html>
++                <default-value>yes</default-value>
++            </option>
++            <option type="text" name="Mantisbt_Project">
++                <_label>CentOS Bug Tracker project</_label>
++                <allow-empty>yes</allow-empty>
++                <_note-html>Specify this only if you needed different project than specified in /etc/os-release</_note-html>
++            </option>
++            <option type="text" name="Mantisbt_ProjectVersion">
++                <_label>CentOS Bug Tracker project version</_label>
++                <allow-empty>yes</allow-empty>
++                <_note-html>Specify this only if you needed different project version than specified in /etc/os-release</_note-html>
++            </option>
++            <option type="text" name="http_proxy">
++                <_label>HTTP Proxy</_label>
++                <allow-empty>yes</allow-empty>
++                <_note-html>Sets the proxy server to use for HTTP</_note-html>
++            </option>
++            <option type="text" name="HTTPS_PROXY">
++                <_label>HTTPS Proxy</_label>
++                <allow-empty>yes</allow-empty>
++                <_note-html>Sets the proxy server to use for HTTPS</_note-html>
++            </option>
++            <option type="bool" name="Mantisbt_CreatePrivate">
++                <_label>Restrict access</_label>
++                <_note-html>Restrict access to the created CentOS Bug Tracker issue allowing only users from specified groups to view it (see advanced settings for more details)</_note-html>
++                <default-value>no</default-value>
++            </option>
++        </advanced-options>
++    </options>
++</event>
+diff --git a/src/plugins/reporter-mantisbt.c b/src/plugins/reporter-mantisbt.c
+new file mode 100644
+index 0000000..d281cdb
+--- /dev/null
++++ b/src/plugins/reporter-mantisbt.c
+@@ -0,0 +1,696 @@
++/*
++    Copyright (C) 2014  ABRT team
++    Copyright (C) 2014  RedHat Inc
++
++    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 2 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, write to the Free Software Foundation, Inc., 51
++    Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
++*/
++
++#include "internal_libreport.h"
++#include "client.h"
++#include "mantisbt.h"
++#include "problem_report.h"
++
++static void
++parse_osinfo_for_mantisbt(map_string_t *osinfo, char** project, char** version)
++{
++    const char *name = get_map_string_item_or_NULL(osinfo, "CENTOS_MANTISBT_PROJECT");
++    if (!name)
++        name = get_map_string_item_or_NULL(osinfo, OSINFO_NAME);
++
++    const char *version_id = get_map_string_item_or_NULL(osinfo, "CENTOS_MANTISBT_PROJECT_VERSION");
++    if (!version_id)
++        version_id = get_map_string_item_or_NULL(osinfo, OSINFO_VERSION_ID);
++
++    if (name && version_id)
++    {
++        *project = xstrdup(name);
++        *version = xstrdup(version_id);
++        return;
++    }
++
++    /* something bad happend */
++    *project = NULL;
++    *version = NULL;
++}
++
++static char *
++ask_mantisbt_login(const char *message)
++{
++    char *login = ask(message);
++    if (login == NULL || login[0] == '\0')
++    {
++        set_xfunc_error_retval(EXIT_CANCEL_BY_USER);
++        error_msg_and_die(_("Can't continue without login"));
++    }
++
++    return login;
++}
++
++static char *
++ask_mantisbt_password(const char *message)
++{
++    char *password = ask_password(message);
++    /* TODO: this should be fixed in ask_password() as other tools have the same problem */
++    putchar('\n');
++    if (password == NULL || password[0] == '\0')
++    {
++        set_xfunc_error_retval(EXIT_CANCEL_BY_USER);
++        error_msg_and_die(_("Can't continue without password"));
++    }
++
++    return password;
++}
++
++static void
++ask_mantisbt_credentials(mantisbt_settings_t *settings, const char *pre_message)
++{
++    free(settings->m_login);
++    free(settings->m_password);
++
++    char *question = xasprintf("%s %s", pre_message, _("Please enter your CentOS Bug Tracker login:"));
++    settings->m_login = ask_mantisbt_login(question);
++    free(question);
++
++    question = xasprintf("%s %s '%s':", pre_message, _("Please enter the password for"), settings->m_login);
++    settings->m_password = ask_mantisbt_password(question);
++    free(question);
++
++    return;
++}
++
++static void
++verify_credentials(mantisbt_settings_t *settings)
++{
++    if (settings->m_login[0] == '\0' || settings->m_password[0] == '\0')
++        ask_mantisbt_credentials(settings, _("Credentials are not provided by configuration."));
++
++    while (true)
++    {
++        soap_request_t *req = soap_request_new_for_method("mc_login");
++        soap_request_add_credentials_parameter(req, settings);
++
++        mantisbt_result_t *result = mantisbt_soap_call(settings, req);
++        soap_request_free(req);
++
++        if (g_verbose > 2)
++        {
++            GList *ids = response_get_main_ids_list(result->mr_body);
++            if (ids != NULL)
++                log("%s", (char *)ids->data);
++            response_values_free(ids);
++        }
++
++        int result_val = result->mr_http_resp_code;
++        mantisbt_result_free(result);
++
++        if (result_val == 200)
++            return;
++
++        ask_mantisbt_credentials(settings, _("Invalid password or login."));
++    }
++}
++
++static void
++set_settings(mantisbt_settings_t *m, map_string_t *settings, struct dump_dir *dd)
++{
++    const char *environ;
++
++    environ = getenv("Mantisbt_Login");
++    m->m_login = xstrdup(environ ? environ : get_map_string_item_or_empty(settings, "Login"));
++
++    environ = getenv("Mantisbt_Password");
++    m->m_password = xstrdup(environ ? environ : get_map_string_item_or_empty(settings, "Password"));
++
++    environ = getenv("Mantisbt_MantisbtURL");
++    m->m_mantisbt_url = environ ? environ : get_map_string_item_or_empty(settings, "MantisbtURL");
++    if (!m->m_mantisbt_url[0])
++        m->m_mantisbt_url = "https://bugs.centos.org/";
++    else
++    {
++        /* We don't want trailing '/': "https://host/dir/" -> "https://host/dir" */
++        char *last_slash = strrchr(m->m_mantisbt_url, '/');
++        if (last_slash && last_slash[1] == '\0')
++            *last_slash = '\0';
++    }
++    m->m_mantisbt_soap_url = concat_path_file(m->m_mantisbt_url, "api/soap/mantisconnect.php");
++
++    environ = getenv("Mantisbt_Project");
++    if (environ)
++    {
++        m->m_project = xstrdup(environ);
++        environ = getenv("Mantisbt_ProjectVersion");
++        if (environ)
++            m->m_project_version = xstrdup(environ);
++    }
++    else
++    {
++        const char *option = get_map_string_item_or_NULL(settings, "Project");
++        if (option)
++            m->m_project = xstrdup(option);
++        option = get_map_string_item_or_NULL(settings, "ProjectVersion");
++        if (option)
++            m->m_project_version = xstrdup(option);
++    }
++
++    if (!m->m_project || !*m->m_project) /* if not overridden or empty... */
++    {
++        free(m->m_project);
++        free(m->m_project_version);
++
++        if (dd != NULL)
++        {
++            map_string_t *osinfo = new_map_string();
++
++            char *os_info_data = dd_load_text(dd, FILENAME_OS_INFO);
++            parse_osinfo(os_info_data, osinfo);
++            free(os_info_data);
++
++            parse_osinfo_for_mantisbt(osinfo, &m->m_project, &m->m_project_version);
++            free_map_string(osinfo);
++        }
++    }
++
++    environ = getenv("Mantisbt_SSLVerify");
++    m->m_ssl_verify = string_to_bool(environ ? environ : get_map_string_item_or_empty(settings, "SSLVerify"));
++
++    environ = getenv("Mantisbt_DontMatchComponents");
++    m->m_DontMatchComponents = environ ? environ : get_map_string_item_or_empty(settings, "DontMatchComponents");
++
++    environ = getenv(CREATE_PRIVATE_TICKET);
++    if (environ)
++        m->m_create_private = string_to_bool(environ);
++
++    if (!m->m_create_private)
++    {
++        environ = getenv("Mantisbt_CreatePrivate");
++        m->m_create_private = string_to_bool(environ ? environ : get_map_string_item_or_empty(settings, "CreatePrivate"));
++    }
++    log_notice("create private CentOS Bug Tracker ticket: '%s'", m->m_create_private ? "YES": "NO");
++}
++
++int main(int argc, char **argv)
++{
++    abrt_init(argv);
++
++    /* I18n */
++    setlocale(LC_ALL, "");
++#if ENABLE_NLS
++    bindtextdomain(PACKAGE, LOCALEDIR);
++    textdomain(PACKAGE);
++#endif
++
++    const char *program_usage_string = _(
++        "\n& [-vf] [-c CONFFILE]... [-F FMTFILE] [-A FMTFILE2] -d DIR"
++        "\nor:"
++        "\n& [-v] [-c CONFFILE]... [-d DIR] -t[ID] FILE..."
++        "\nor:"
++        "\n& [-v] [-c CONFFILE]... [-d DIR] -t[ID] -w"
++        "\nor:"
++        "\n& [-v] [-c CONFFILE]... -h DUPHASH"
++        "\n"
++        "\nReports problem to CentOS Bug Tracker."
++        "\n"
++        "\nThe tool reads DIR. Then it tries to find an issue"
++        "\nwith the same abrt_hash in custom field 'abrt_hash'."
++        "\n"
++        "\nIf such issue is not found, then a new issue is created. Elements of DIR"
++        "\nare stored in the issue as part of issue description or as attachments,"
++        "\ndepending on their type and size."
++        "\n"
++        "\nOtherwise, if such issue is found and it is marked as CLOSED DUPLICATE,"
++        "\nthe tool follows the chain of duplicates until it finds a non-DUPLICATE issue."
++        "\nThe tool adds a new comment to found issue."
++        "\n"
++        "\nThe URL to new or modified issue is printed to stdout and recorded in"
++        "\n'reported_to' element."
++        "\n"
++        "\nOption -t uploads FILEs to the already created issue on CentOS Bug Tracker site."
++        "\nThe issue ID is retrieved from directory specified by -d DIR."
++        "\nIf problem data in DIR was never reported to CentOS Bug Tracker, upload will fail."
++        "\n"
++        "\nOption -tID uploads FILEs to the issue with specified ID on CentOS Bug Tracker site."
++        "\n-d DIR is ignored."
++        "\n"
++        "\nOption -r sets the last url from reporter_to element which is prefixed with"
++        "\nTRACKER_NAME to URL field. This option is applied only when a new issue is to be"
++        "\nfiled. The default value is 'ABRT Server'"
++        "\n"
++        "\nIf not specified, CONFFILE defaults to "CONF_DIR"/plugins/mantisb.conf"
++        "\nIts lines should have 'PARAM = VALUE' format."
++        "\nRecognized string parameters: MantisbtURL, Login, Password, Project, ProjectVersion."
++        "\nRecognized boolean parameter (VALUE should be 1/0, yes/no): SSLVerify, CreatePrivate."
++        "\nParameters can be overridden via $Mantisbt_PARAM environment variables."
++        "\n"
++        "\nFMTFILE and FMTFILE2 default to "CONF_DIR"/plugins/mantisbt_format.conf"
++    );
++
++    enum {
++        OPT_v = 1 << 0,
++        OPT_d = 1 << 1,
++        OPT_c = 1 << 2,
++        OPT_F = 1 << 3,
++        OPT_A = 1 << 4,
++        OPT_t = 1 << 5,
++        OPT_f = 1 << 6,
++        OPT_h = 1 << 7,
++        OPT_r = 1 << 8,
++        OPT_D = 1 << 9,
++    };
++
++    const char *dump_dir_name = ".";
++    GList *conf_file = NULL;
++    const char *fmt_file = CONF_DIR"/plugins/mantisbt_format.conf";
++    const char *fmt_file2 = fmt_file;
++    char *abrt_hash = NULL;
++    char *ticket_no = NULL;
++    const char *tracker_str = "ABRT Server";
++    char *debug_str = NULL;
++    mantisbt_settings_t mbt_settings = { 0 };
++    /* Keep enum above and order of options below in sync! */
++    struct options program_options[] = {
++        OPT__VERBOSE(&g_verbose),
++        OPT_STRING(   'd', NULL, &dump_dir_name , "DIR"    , _("Problem directory")),
++        OPT_LIST(     'c', NULL, &conf_file     , "FILE"   , _("Configuration file (may be given many times)")),
++        OPT_STRING(   'F', NULL, &fmt_file      , "FILE"   , _("Formatting file for initial comment")),
++        OPT_STRING(   'A', NULL, &fmt_file2     , "FILE"   , _("Formatting file for duplicates")),
++        OPT_OPTSTRING('t', "ticket", &ticket_no , "ID"     , _("Attach FILEs [to issue with this ID]")),
++        OPT_BOOL(     'f', NULL, NULL,                       _("Force reporting even if this problem is already reported")),
++        OPT_STRING(   'h', "duphash", &abrt_hash, "DUPHASH", _("Print BUG_ID which has given DUPHASH")),
++        OPT_STRING(   'r', "tracker", &tracker_str, "TRACKER_NAME", _("A name of bug tracker for an additional URL from 'reported_to'")),
++
++        OPT_OPTSTRING('D', "debug", &debug_str  , "STR"    , _("Debug")),
++        OPT_END()
++    };
++
++    unsigned opts = parse_opts(argc, argv, program_options, program_usage_string);
++    argv += optind;
++
++    export_abrt_envvars(0);
++
++    map_string_t *settings = new_map_string();
++
++    {
++        if (!conf_file)
++            conf_file = g_list_append(conf_file, (char*) CONF_DIR"/plugins/mantisbt.conf");
++        while (conf_file)
++        {
++            char *fn = (char *)conf_file->data;
++            log_notice("Loading settings from '%s'", fn);
++            load_conf_file(fn, settings, /*skip key w/o values:*/ false);
++            log_debug("Loaded '%s'", fn);
++            conf_file = g_list_delete_link(conf_file, conf_file);
++        }
++
++        struct dump_dir *dd = NULL;
++        if (abrt_hash == NULL)
++        {
++            dd = dd_opendir(dump_dir_name, /*flags:*/ 0);
++            if (!dd)
++                error_msg_and_die(_("Can't open problem dir '%s'."), dump_dir_name);
++        }
++
++        set_settings(&mbt_settings, settings, dd);
++        dd_close(dd);
++        /* WRONG! set_settings() does not copy the strings, it merely sets up pointers
++         * to settings[] dictionary:
++         */
++        /*free_map_string(settings);*/
++    }
++
++    /* No connection is opened between client and server. Users authentication
++     * is performed on every SOAP method call. In the first step we verify the
++     * credentials by calling 'mc_login' method.  In the case the credentials are
++     * correctly applies the reporter uses them in the next requests. It is not
++     * necessary to call 'mc_login' method because the method provides only
++     * verification of credentials.
++     */
++    verify_credentials(&mbt_settings);
++
++    if (abrt_hash)
++    {
++        log(_("Looking for similar problems in CentOS Bug Tracker"));
++        GList *ids = mantisbt_search_by_abrt_hash(&mbt_settings, abrt_hash);
++        mantisbt_settings_free(&mbt_settings);
++
++        if (ids == NULL)
++            return EXIT_FAILURE;
++
++        puts(ids->data);
++        response_values_free(ids);
++        return EXIT_SUCCESS;
++    }
++
++    mantisbt_get_project_id_from_name(&mbt_settings);
++
++    if (opts & OPT_t)
++    {
++        if (!argv[0])
++            show_usage_and_die(program_usage_string, program_options);
++
++        if (!ticket_no)
++        {
++            struct dump_dir *dd = dd_opendir(dump_dir_name, /*flags:*/ 0);
++            if (!dd)
++                xfunc_die();
++            report_result_t *reported_to = find_in_reported_to(dd, "CentOS Bug Tracker");
++            dd_close(dd);
++
++            if (!reported_to || !reported_to->url)
++                error_msg_and_die(_("Can't get MantisBT ID because this problem has not yet been reported to MantisBT."));
++
++            char *url = reported_to->url;
++            reported_to->url = NULL;
++            free_report_result(reported_to);
++
++            if (prefixcmp(url, mbt_settings.m_mantisbt_url) != 0)
++                error_msg_and_die(_("This problem has been reported to MantisBT '%s' which differs from the configured MantisBT '%s'."), url, mbt_settings.m_mantisbt_url);
++
++            ticket_no = strrchr(url, '=');
++            if (!ticket_no)
++                error_msg_and_die(_("Malformed url to MantisBT '%s'."), url);
++
++            /* won't ever call free on it - it simplifies the code a lot */
++            ticket_no = xstrdup(ticket_no + 1);
++            log(_("Using CentOS Bug Tracker ID '%s'"), ticket_no);
++        }
++
++        /* Attach files to existing MantisBT issues */
++        while (*argv)
++        {
++            const char *path = *argv++;
++            char *filename = basename(path);
++            log(_("Attaching file '%s' to issue %s"), filename, ticket_no);
++            mantisbt_attach_file(&mbt_settings, ticket_no, filename, path);
++        }
++
++        return 0;
++    }
++
++    /* Create new issue in MantisBT */
++
++    if (!(opts & OPT_f))
++    {
++        struct dump_dir *dd = dd_opendir(dump_dir_name, /*flags:*/ 0);
++        if (!dd)
++            xfunc_die();
++        report_result_t *reported_to = find_in_reported_to(dd, "CentOS Bug Tracker");
++        dd_close(dd);
++
++        if (reported_to && reported_to->url)
++        {
++            char *msg = xasprintf(_("This problem was already reported to CentOS Bug Tracker (see '%s')."
++                            " Do you still want to create a new issue?"),
++                            reported_to->url);
++            int yes = ask_yes_no(msg);
++            free(msg);
++            if (!yes)
++                return 0;
++        }
++        free_report_result(reported_to);
++    }
++
++    problem_data_t *problem_data = create_problem_data_for_reporting(dump_dir_name);
++    if (!problem_data)
++        xfunc_die(); /* create_problem_data_for_reporting already emitted error msg */
++
++    const char *category = problem_data_get_content_or_die(problem_data, FILENAME_COMPONENT);
++    const char *duphash   = problem_data_get_content_or_die(problem_data, FILENAME_DUPHASH);
++
++    if (opts & OPT_D)
++    {
++        problem_formatter_t *pf = problem_formatter_new();
++        problem_formatter_add_section(pf, PR_SEC_ADDITIONAL_INFO, /* optional section */ 0);
++
++        if (problem_formatter_load_file(pf, fmt_file))
++            error_msg_and_die("Invalid format file: %s", fmt_file);
++
++        problem_report_t *pr = NULL;
++        if (problem_formatter_generate_report(pf, problem_data, &pr))
++            error_msg_and_die("Failed to format issue report from problem data");
++
++        printf("summary: %s\n"
++                "\n"
++                "Description:\n%s\n"
++                "Additional info:\n%s\n"
++                , problem_report_get_summary(pr)
++                , problem_report_get_description(pr)
++                , problem_report_get_section(pr, PR_SEC_ADDITIONAL_INFO)
++        );
++
++        puts("attachments:");
++        for (GList *a = problem_report_get_attachments(pr); a != NULL; a = g_list_next(a))
++            printf(" %s\n", (const char *)a->data);
++
++        problem_report_free(pr);
++        problem_formatter_free(pf);
++        exit(0);
++    }
++
++     int bug_id = 0;
++
++    /* If REMOTE_RESULT contains "DUPLICATE 12345", we consider it a dup of 12345
++     * and won't search on MantisBT server.
++     */
++    char *remote_result;
++    remote_result = problem_data_get_content_or_NULL(problem_data, FILENAME_REMOTE_RESULT);
++    if (remote_result)
++    {
++        char *cmd = strtok(remote_result, " \n");
++        char *id = strtok(NULL, " \n");
++
++        if (!prefixcmp(cmd, "DUPLICATE"))
++        {
++            errno = 0;
++            char *e;
++            bug_id = strtoul(id, &e, 10);
++            if (errno || id == e || *e != '\0' || bug_id > INT_MAX)
++            {
++                /* error / no digits / illegal trailing chars / too big a number */
++                bug_id = 0;
++            }
++        }
++    }
++
++    mantisbt_issue_info_t *ii;
++    if (!bug_id)
++    {
++        log(_("Checking for duplicates"));
++
++        int existing_id = -1;
++        int crossver_id = -1;
++        {
++            /* Figure out whether we want to match category
++             * when doing dup search.
++             */
++            const char *category_substitute = is_in_comma_separated_list(category, mbt_settings.m_DontMatchComponents) ? NULL : category;
++
++            /* We don't do dup detection across versions (see below why),
++             * but we do add a note if cross-version potential dup exists.
++             * For that, we search for cross version dups first:
++             */
++            // SOAP API searching method is not in the final version, it's possible the project will be string
++            GList *crossver_bugs_ids = mantisbt_search_duplicate_issues(&mbt_settings, category_substitute, /*version*/ NULL, duphash);
++
++            unsigned crossver_bugs_count = g_list_length(crossver_bugs_ids);
++            log_debug("CentOS Bug Tracker has %i reports with duphash '%s' including cross-version ones",
++                    crossver_bugs_count, duphash);
++            if (crossver_bugs_count > 0)
++                crossver_id = atoi(g_list_first(crossver_bugs_ids)->data);
++
++            if (crossver_bugs_count > 0)
++            {
++                // SOAP API searching method is not in the final version, it's possible the project will be string
++                GList *dup_bugs_ids = mantisbt_search_duplicate_issues(&mbt_settings, category_substitute, mbt_settings.m_project_version, duphash);
++
++                unsigned dup_bugs_count =  g_list_length(dup_bugs_ids);
++                log_debug("CentOS Bug Tracker has %i reports with duphash '%s'",
++                        dup_bugs_count, duphash);
++                if (dup_bugs_count > 0)
++                    existing_id = atoi(g_list_first(dup_bugs_ids)->data);
++            }
++        }
++
++        if (existing_id < 0)
++        {
++            /* Create new issue */
++            log(_("Creating a new issue"));
++            problem_formatter_t *pf = problem_formatter_new();
++            problem_formatter_add_section(pf, PR_SEC_ADDITIONAL_INFO, 0);
++
++            if (problem_formatter_load_file(pf, fmt_file))
++                error_msg_and_die(_("Invalid format file: %s"), fmt_file);
++
++            problem_report_t *pr = NULL;
++            if (problem_formatter_generate_report(pf, problem_data, &pr))
++                error_msg_and_die(_("Failed to format problem data"));
++
++            if (crossver_id >= 0)
++                problem_report_buffer_printf(
++                        problem_report_get_buffer(pr, PR_SEC_DESCRIPTION),
++                        "\nPotential duplicate: issue %u\n", crossver_id);
++
++            problem_formatter_free(pf);
++
++            /* get tracker URL if exists */
++            struct dump_dir *dd = dd_opendir(dump_dir_name, 0);
++            char *tracker_url = NULL;
++            if (dd)
++            {
++                report_result_t *reported_to = find_in_reported_to(dd, tracker_str);
++                dd_close(dd);
++
++                if (reported_to && reported_to->url)
++                {
++                    log(_("Adding External URL to issue"));
++                    tracker_url = xstrdup(reported_to->url);
++                    free_report_result(reported_to);
++                }
++            }
++
++            int new_id = mantisbt_create_new_issue(&mbt_settings, problem_data, pr, tracker_url);
++
++            free(tracker_url);
++
++            if (new_id == -1)
++                return EXIT_FAILURE;
++
++            log(_("Adding attachments to issue %i"), new_id);
++            char *new_id_str = xasprintf("%u", new_id);
++
++            for (GList *a = problem_report_get_attachments(pr); a != NULL; a = g_list_next(a))
++            {
++                const char *item_name = (const char *)a->data;
++                struct problem_item *item = problem_data_get_item_or_NULL(problem_data, item_name);
++                if (!item)
++                    continue;
++                else if (item->flags & CD_FLAG_TXT)
++                    mantisbt_attach_data(&mbt_settings, new_id_str, item_name, item->content, strlen(item->content));
++                else if (item->flags & CD_FLAG_BIN)
++                    mantisbt_attach_file(&mbt_settings, new_id_str, item_name, item->content);
++            }
++
++            free(new_id_str);
++            problem_report_free(pr);
++            ii = mantisbt_issue_info_new();
++            ii->mii_id = new_id;
++            ii->mii_status = xstrdup("new");
++
++            goto finish;
++        }
++
++        bug_id = existing_id;
++    }
++
++    ii = mantisbt_get_issue_info(&mbt_settings, bug_id);
++
++    log(_("Bug is already reported: %i"), ii->mii_id);
++
++    /* Follow duplicates */
++    if ((strcmp(ii->mii_status, "closed") == 0)
++     && (strcmp(ii->mii_resolution, "duplicate") == 0)
++    ) {
++        mantisbt_issue_info_t *origin = mantisbt_find_origin_bug_closed_duplicate(&mbt_settings, ii);
++        if (origin)
++        {
++            mantisbt_issue_info_free(ii);
++            ii = origin;
++        }
++    }
++
++    /* TODO CC list
++     * Is no MantisBT SOAP API method which allows adding users to CC list
++     * without updating issue.
++     */
++
++    /* Add comment and bt */
++    const char *comment = problem_data_get_content_or_NULL(problem_data, FILENAME_COMMENT);
++    if (comment && comment[0])
++    {
++        problem_formatter_t *pf = problem_formatter_new();
++
++        if (problem_formatter_load_file(pf, fmt_file2))
++            error_msg_and_die(_("Invalid duplicate format file: '%s"), fmt_file2);
++
++        problem_report_t *pr;
++        if (problem_formatter_generate_report(pf, problem_data, &pr))
++            error_msg_and_die(_("Failed to format duplicate comment from problem data"));
++
++        const char *mbtcomment = problem_report_get_description(pr);
++
++        int dup_comment = is_comment_dup(ii->mii_notes, mbtcomment);
++        if (!dup_comment)
++        {
++            log(_("Adding new comment to issue %d"), ii->mii_id);
++            mantisbt_add_issue_note(&mbt_settings, ii->mii_id, mbtcomment);
++
++            const char *bt = problem_data_get_content_or_NULL(problem_data, FILENAME_BACKTRACE);
++            unsigned rating = 0;
++            const char *rating_str = problem_data_get_content_or_NULL(problem_data, FILENAME_RATING);
++            /* python doesn't have rating file */
++            if (rating_str)
++                rating = xatou(rating_str);
++            if (bt && rating > ii->mii_best_bt_rating)
++            {
++                char *bug_id_str = xasprintf("%i", ii->mii_id);
++
++                log(_("Attaching better backtrace"));
++
++                // find unique filename of attachment
++                char *name = NULL;
++                for (int i = 0;; ++i)
++                {
++                    if (i == 0)
++                        name = xasprintf("%s", FILENAME_BACKTRACE);
++                    else
++                        name = xasprintf("%s%d", FILENAME_BACKTRACE, i);
++
++                    if (g_list_find_custom(ii->mii_attachments, name, (GCompareFunc) strcmp) == NULL)
++                        break;
++
++                    free(name);
++                }
++                mantisbt_attach_data(&mbt_settings, bug_id_str, name, bt, strlen(bt));
++
++                free(name);
++                free(bug_id_str);
++            }
++        }
++        else
++            log(_("Found the same comment in the issue history, not adding a new one"));
++
++        problem_report_free(pr);
++        problem_formatter_free(pf);
++    }
++
++finish:
++    log(_("Status: %s%s%s %s/view.php?id=%u"),
++                ii->mii_status,
++                ii->mii_resolution ? " " : "",
++                ii->mii_resolution ? ii->mii_resolution : "",
++                mbt_settings.m_mantisbt_url,
++                ii->mii_id);
++
++    struct dump_dir *dd = dd_opendir(dump_dir_name, /*flags:*/ 0);
++    if (dd)
++    {
++        char *msg = xasprintf("CentOS Bug Tracker: URL=%s/view.php?id=%u", mbt_settings.m_mantisbt_url, ii->mii_id);
++        add_reported_to(dd, msg);
++        free(msg);
++        dd_close(dd);
++    }
++
++    mantisbt_settings_free(&mbt_settings);
++    return 0;
++}
+diff --git a/src/workflows/Makefile.am b/src/workflows/Makefile.am
+index 0fc1019..5b8376a 100644
+--- a/src/workflows/Makefile.am
++++ b/src/workflows/Makefile.am
+@@ -22,6 +22,18 @@ dist_workflows_DATA = \
+     workflow_Logger.xml \
+     workflow_LoggerCCpp.xml
+ 
++if BUILD_MANTISBT
++dist_workflows_DATA += \
++    workflow_CentOSCCpp.xml \
++    workflow_CentOSKerneloops.xml \
++    workflow_CentOSPython.xml \
++    workflow_CentOSPython3.xml \
++    workflow_CentOSVmcore.xml \
++    workflow_CentOSXorg.xml \
++    workflow_CentOSLibreport.xml \
++    workflow_CentOSJava.xml
++endif
++
+ if BUILD_BUGZILLA
+ dist_workflows_DATA += \
+     workflow_AnacondaFedora.xml \
+@@ -44,7 +56,8 @@ dist_workflowsdef_DATA =\
+     report_rhel.conf \
+     report_mailx.conf \
+     report_logger.conf \
+-    report_uploader.conf
++    report_uploader.conf \
++    report_centos.conf
+ 
+ if BUILD_BUGZILLA
+ dist_workflowsdef_DATA += \
+diff --git a/src/workflows/report_centos.conf b/src/workflows/report_centos.conf
+new file mode 100644
+index 0000000..07b0d40
+--- /dev/null
++++ b/src/workflows/report_centos.conf
+@@ -0,0 +1,31 @@
++EVENT=workflow_CentOSLibreport analyzer=libreport
++# this is just a meta event which consists of other events
++# the list is defined in the xml file
++
++EVENT=workflow_CentOSCCpp analyzer=CCpp
++# this is just a meta event which consists of other events
++# the list is defined in the xml file
++
++EVENT=workflow_CentOSPython analyzer=Python component!=anaconda
++# this is just a meta event which consists of other events
++# the list is defined in the xml file
++
++EVENT=workflow_CentOSPython3 analyzer=Python3 component!=anaconda
++# this is just a meta event which consists of other events
++# the list is defined in the xml file
++
++EVENT=workflow_CentOSKerneloops analyzer=Kerneloops
++# this is just a meta event which consists of other events
++# the list is defined in the xml file
++
++EVENT=workflow_CentOSVmcore analyzer=vmcore
++# this is just a meta event which consists of other events
++# the list is defined in the xml file
++
++EVENT=workflow_CentOSXorg analyzer=xorg
++# this is just a meta event which consists of other events
++# the list is defined in the xml file
++
++EVENT=workflow_CentOSJava analyzer=Java
++# this is just a meta event which consists of other events
++# the list is defined in the xml file
+diff --git a/src/workflows/workflow_CentOSCCpp.xml.in b/src/workflows/workflow_CentOSCCpp.xml.in
+new file mode 100644
+index 0000000..ea94d56
+--- /dev/null
++++ b/src/workflows/workflow_CentOSCCpp.xml.in
+@@ -0,0 +1,12 @@
++<?xml version="1.0" encoding="UTF-8" ?>
++<workflow>
++    <_name>Report to CentOS Bug Tracker</_name>
++    <_description>Process the C/C++ crash using the CentOS infrastructure</_description>
++
++    <events>
++        <event>report_uReport</event>
++        <event>collect_*</event>
++        <event>analyze_CCpp</event>
++        <event>report_CentOSBugTracker</event>
++    </events>
++</workflow>
+diff --git a/src/workflows/workflow_CentOSJava.xml.in b/src/workflows/workflow_CentOSJava.xml.in
+new file mode 100644
+index 0000000..89f9295
+--- /dev/null
++++ b/src/workflows/workflow_CentOSJava.xml.in
+@@ -0,0 +1,11 @@
++<?xml version="1.0" encoding="UTF-8" ?>
++<workflow>
++    <_name>Report to CentOS Bug Tracker</_name>
++    <_description>Process the Java exception using the CentOS infrastructure</_description>
++
++    <events>
++        <event>report_uReport</event>
++        <event>collect_*</event>
++        <event>report_CentOSBugTracker</event>
++    </events>
++</workflow>
+diff --git a/src/workflows/workflow_CentOSKerneloops.xml.in b/src/workflows/workflow_CentOSKerneloops.xml.in
+new file mode 100644
+index 0000000..c43c681
+--- /dev/null
++++ b/src/workflows/workflow_CentOSKerneloops.xml.in
+@@ -0,0 +1,11 @@
++<?xml version="1.0" encoding="UTF-8" ?>
++<workflow>
++    <_name>Report to CentOS Bug Tracker</_name>
++    <_description>Process the kerneloops using the CentOS infrastructure</_description>
++
++    <events>
++        <event>report_uReport</event>
++        <event>collect_*</event>
++        <event>report_CentOSBugTracker</event>
++    </events>
++</workflow>
+diff --git a/src/workflows/workflow_CentOSLibreport.xml.in b/src/workflows/workflow_CentOSLibreport.xml.in
+new file mode 100644
+index 0000000..2f6ed82
+--- /dev/null
++++ b/src/workflows/workflow_CentOSLibreport.xml.in
+@@ -0,0 +1,9 @@
++<?xml version="1.0" encoding="UTF-8" ?>
++<workflow>
++    <_name>Report to CentOS Bug Tracker</_name>
++    <_description>Process the problem using the CentOS infrastructure</_description>
++
++    <events>
++        <event>report_CentOSBugTracker</event>
++    </events>
++</workflow>
+diff --git a/src/workflows/workflow_CentOSPython.xml.in b/src/workflows/workflow_CentOSPython.xml.in
+new file mode 100644
+index 0000000..7e26c6c
+--- /dev/null
++++ b/src/workflows/workflow_CentOSPython.xml.in
+@@ -0,0 +1,11 @@
++<?xml version="1.0" encoding="UTF-8" ?>
++<workflow>
++    <_name>Report to CentOS Bug Tracker</_name>
++    <_description>Process the python exception using the CentOS infrastructure</_description>
++
++    <events>
++        <event>report_uReport</event>
++        <event>collect_*</event>
++        <event>report_CentOSBugTracker</event>
++    </events>
++</workflow>
+diff --git a/src/workflows/workflow_CentOSPython3.xml.in b/src/workflows/workflow_CentOSPython3.xml.in
+new file mode 100644
+index 0000000..f1dd8a9
+--- /dev/null
++++ b/src/workflows/workflow_CentOSPython3.xml.in
+@@ -0,0 +1,11 @@
++<?xml version="1.0" encoding="UTF-8" ?>
++<workflow>
++    <_name>Report to CentOS Bug Tracker</_name>
++    <_description>Process the python 3 exception using the CentOS infrastructure</_description>
++
++    <events>
++        <event>report_uReport</event>
++        <event>collect_*</event>
++        <event>report_CentOSBugTracker</event>
++    </events>
++</workflow>
+diff --git a/src/workflows/workflow_CentOSVmcore.xml.in b/src/workflows/workflow_CentOSVmcore.xml.in
+new file mode 100644
+index 0000000..c876594
+--- /dev/null
++++ b/src/workflows/workflow_CentOSVmcore.xml.in
+@@ -0,0 +1,12 @@
++<?xml version="1.0" encoding="UTF-8" ?>
++<workflow>
++    <_name>Report to CentOS Bug Tracker</_name>
++    <_description>Process the kernel crash using the CentOS infrastructure</_description>
++
++    <events>
++        <event>analyze_VMcore</event>
++        <event>report_uReport</event>
++        <event>collect_*</event>
++        <event>report_CentOSBugTracker</event>
++    </events>
++</workflow>
+diff --git a/src/workflows/workflow_CentOSXorg.xml.in b/src/workflows/workflow_CentOSXorg.xml.in
+new file mode 100644
+index 0000000..bf52221
+--- /dev/null
++++ b/src/workflows/workflow_CentOSXorg.xml.in
+@@ -0,0 +1,9 @@
++<?xml version="1.0" encoding="UTF-8" ?>
++<workflow>
++    <_name>Report to CentOS Bug Tracker</_name>
++    <_description>Process the X Server problem using the CentOS infrastructure</_description>
++
++    <events>
++        <event>report_CentOSBugTracker</event>
++    </events>
++</workflow>
+-- 
+1.8.3.1
+
diff --git a/SPECS/libreport.spec b/SPECS/libreport.spec
index 737f20f..d2284a2 100644
--- a/SPECS/libreport.spec
+++ b/SPECS/libreport.spec
@@ -7,7 +7,7 @@
 Summary: Generic library for reporting various problems
 Name: libreport
 Version: 2.1.11
-Release: 21%{?dist}
+Release: 22%{?dist}
 License: GPLv2+
 Group: System Environment/Libraries
 URL: https://fedorahosted.org/abrt/
@@ -139,6 +139,16 @@ Patch123: 0123-lib-add-a-clone-function-for-map_string_t.patch
 Patch124: 0124-ureport-introduce-HTTPAuth.patch
 Patch125: 0125-Do-not-use-bool-in-OPT_BOOL-macro-it-expects-int.patch
 Patch126: libreport-centos-branding-changes.patch
+Patch127: 0127-lib-add-Problem-Format-API.patch
+#Patch128: 0128-bugzilla-port-to-Problem-Format-API.patch
+#Patch129: 0129-spec-reflect-changes-due-to-Problem-Report-API.patch
+Patch130: 0130-lib-created-a-new-lib-file-for-reporters.patch
+#Patch131: 0131-spec-changed-spec-file-to-work-with-last-commit.patch
+#Patch132: 0132-testsuite-disabled-a-test-testsuite.at-because-of-co.patch
+Patch133: 0133-ureport-set-url-to-public-faf-server.patch
+Patch134: 0134-conf-changed-URL-for-sending-uReport.patch
+Patch1000: 1000-reporter-mantisbt-first-version-of-the-reporter-mant.patch
+#Patch1001: 1001-spec-changed-spec-file-to-work-with-reporter-mantisb.patch
 
 # git is need for '%%autosetup -S git' which automatically applies all the
 # patches above. Please, be aware that the patches must be generated
@@ -315,6 +325,25 @@ Group: System Environment/Libraries
 Requires: %{name} = %{version}-%{release}
 Requires: libreport-web = %{version}-%{release}
 
+%package plugin-mantisbt
+Summary: %{name}'s mantisbt plugin
+Group: System Environment/Libraries
+Requires: %{name} = %{version}-%{release}
+Requires: libreport-web = %{version}-%{release}
+
+%description plugin-mantisbt
+Plugin to report bugs into the mantisbt.
+
+%package centos
+Summary: %{name}'s CentOS Bug Tracker workflow
+Group: System Environment/Libraries
+Requires: %{name} = %{version}-%{release}
+Requires: libreport-web = %{version}-%{release}
+Requires: libreport-plugin-mantisbt = %{version}-%{release}
+
+%description centos
+Workflows to report issues into the CentOS Bug Tracker.
+
 %package plugin-ureport
 Summary: %{name}'s micro report plugin
 BuildRequires: json-c-devel
@@ -562,12 +591,14 @@ gtk-update-icon-cache %{_datadir}/icons/hicolor &>/dev/null || :
 %{_includedir}/libreport/dump_dir.h
 %{_includedir}/libreport/event_config.h
 %{_includedir}/libreport/problem_data.h
+%{_includedir}/libreport/problem_report.h
 %{_includedir}/libreport/report.h
 %{_includedir}/libreport/run_event.h
 %{_includedir}/libreport/file_obj.h
 %{_includedir}/libreport/config_item_info.h
 %{_includedir}/libreport/workflow.h
 %{_includedir}/libreport/ureport.h
+%{_includedir}/libreport/reporters.h
 # Private api headers:
 %{_includedir}/libreport/internal_abrt_dbus.h
 %{_includedir}/libreport/internal_libreport.h
@@ -687,6 +718,29 @@ gtk-update-icon-cache %{_datadir}/icons/hicolor &>/dev/null || :
 %{_mandir}/man5/bugzilla_format_kernel.conf.5.*
 %{_bindir}/reporter-bugzilla
 
+%files plugin-mantisbt
+%defattr(-,root,root,-)
+%config(noreplace) %{_sysconfdir}/libreport/plugins/mantisbt.conf
+%{_datadir}/%{name}/conf.d/plugins/mantisbt.conf
+%config(noreplace) %{_sysconfdir}/libreport/plugins/mantisbt_format.conf
+%config(noreplace) %{_sysconfdir}/libreport/plugins/mantisbt_formatdup.conf
+%{_bindir}/reporter-mantisbt
+
+%files centos
+%{_datadir}/%{name}/workflows/workflow_CentOSCCpp.xml
+%{_datadir}/%{name}/workflows/workflow_CentOSKerneloops.xml
+%{_datadir}/%{name}/workflows/workflow_CentOSPython.xml
+%{_datadir}/%{name}/workflows/workflow_CentOSPython3.xml
+%{_datadir}/%{name}/workflows/workflow_CentOSVmcore.xml
+%{_datadir}/%{name}/workflows/workflow_CentOSXorg.xml
+%{_datadir}/%{name}/workflows/workflow_CentOSLibreport.xml
+%{_datadir}/%{name}/workflows/workflow_CentOSJava.xml
+%config(noreplace) %{_sysconfdir}/libreport/workflows.d/report_centos.conf
+%{_datadir}/%{name}/events/report_CentOSBugTracker.xml
+%config(noreplace) %{_sysconfdir}/libreport/events/report_CentOSBugTracker.conf
+# report_CentOSBugTracker events are shipped by libreport package
+%config(noreplace) %{_sysconfdir}/libreport/events.d/centos_report_event.conf
+
 %files plugin-rhtsupport
 %defattr(-,root,root,-)
 %config(noreplace) %{_sysconfdir}/libreport/plugins/rhtsupport.conf
@@ -780,6 +834,11 @@ gtk-update-icon-cache %{_datadir}/icons/hicolor &>/dev/null || :
 
 
 %changelog
+* Mon Mar 09 2015 Matej Habrnal <mhabrnal@redhat.com> = 2.1.11-22
+- introduce reporter mantisbt
+- add problem format api
+- set url to public faf server
+
 * Thu Mar 05 2015 CentOS Sources <bugs@centos.org> - 2.1.11-21.el7.centos
 - Roll in patch to remove RH support for libreport
 
-- 
1.8.3.1

