LCOV - code coverage report
Current view: top level - common - output_html.c (source / functions) Hit Total Coverage
Test: Pacemaker code coverage Lines: 0 227 0.0 %
Date: 2024-05-07 11:09:47 Functions: 0 21 0.0 %

          Line data    Source code
       1             : /*
       2             :  * Copyright 2019-2024 the Pacemaker project contributors
       3             :  *
       4             :  * The version control history for this file may have further details.
       5             :  *
       6             :  * This source code is licensed under the GNU Lesser General Public License
       7             :  * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
       8             :  */
       9             : 
      10             : #include <crm_internal.h>
      11             : 
      12             : #include <ctype.h>
      13             : #include <libxml/HTMLtree.h>
      14             : #include <stdarg.h>
      15             : #include <stdlib.h>
      16             : #include <stdio.h>
      17             : 
      18             : #include <crm/common/cmdline_internal.h>
      19             : #include <crm/common/xml.h>
      20             : 
      21             : static const char *stylesheet_default =
      22             :     "." PCMK__VALUE_BOLD " { font-weight: bold }\n"
      23             : 
      24             :     "." PCMK_VALUE_ONLINE " { color: green }\n"
      25             :     "." PCMK_VALUE_OFFLINE " { color: red }\n"
      26             :     "." PCMK__VALUE_MAINT " { color: blue }\n"
      27             :     "." PCMK_VALUE_STANDBY " { color: blue }\n"
      28             :     "." PCMK__VALUE_HEALTH_RED " { color: red }\n"
      29             :     "." PCMK__VALUE_HEALTH_YELLOW " { color: GoldenRod }\n"
      30             : 
      31             :     "." PCMK__VALUE_RSC_FAILED " { color: red }\n"
      32             :     "." PCMK__VALUE_RSC_FAILURE_IGNORED " { color: DarkGreen }\n"
      33             :     "." PCMK__VALUE_RSC_MANAGED " { color: blue }\n"
      34             :     "." PCMK__VALUE_RSC_MULTIPLE " { color: orange }\n"
      35             :     "." PCMK__VALUE_RSC_OK " { color: green }\n"
      36             : 
      37             :     "." PCMK__VALUE_WARNING " { color: red; font-weight: bold }";
      38             : 
      39             : static gboolean cgi_output = FALSE;
      40             : static char *stylesheet_link = NULL;
      41             : static char *title = NULL;
      42             : static GSList *extra_headers = NULL;
      43             : 
      44             : GOptionEntry pcmk__html_output_entries[] = {
      45             :     { "html-cgi", 0, 0, G_OPTION_ARG_NONE, &cgi_output,
      46             :       "Add CGI headers (requires --output-as=html)",
      47             :       NULL },
      48             : 
      49             :     { "html-stylesheet", 0, 0, G_OPTION_ARG_STRING, &stylesheet_link,
      50             :       "Link to an external stylesheet (requires --output-as=html)",
      51             :       "URI" },
      52             : 
      53             :     { "html-title", 0, 0, G_OPTION_ARG_STRING, &title,
      54             :       "Specify a page title (requires --output-as=html)",
      55             :       "TITLE" },
      56             : 
      57             :     { NULL }
      58             : };
      59             : 
      60             : /* The first several elements of this struct must be the same as the first
      61             :  * several elements of private_data_s in lib/common/output_xml.c.  This
      62             :  * struct gets passed to a bunch of the pcmk__output_xml_* functions which
      63             :  * assume an XML private_data_s.  Keeping them laid out the same means this
      64             :  * still works.
      65             :  */
      66             : typedef struct private_data_s {
      67             :     /* Begin members that must match the XML version */
      68             :     xmlNode *root;
      69             :     GQueue *parent_q;
      70             :     GSList *errors;
      71             :     /* End members that must match the XML version */
      72             : } private_data_t;
      73             : 
      74             : static void
      75           0 : html_free_priv(pcmk__output_t *out) {
      76           0 :     private_data_t *priv = NULL;
      77             : 
      78           0 :     if (out == NULL || out->priv == NULL) {
      79           0 :         return;
      80             :     }
      81             : 
      82           0 :     priv = out->priv;
      83             : 
      84           0 :     free_xml(priv->root);
      85             :     /* The elements of parent_q are xmlNodes that are a part of the
      86             :      * priv->root document, so the above line already frees them.  Don't
      87             :      * call g_queue_free_full here.
      88             :      */
      89           0 :     g_queue_free(priv->parent_q);
      90           0 :     g_slist_free_full(priv->errors, free);
      91           0 :     free(priv);
      92           0 :     out->priv = NULL;
      93             : }
      94             : 
      95             : static bool
      96           0 : html_init(pcmk__output_t *out) {
      97           0 :     private_data_t *priv = NULL;
      98             : 
      99           0 :     CRM_ASSERT(out != NULL);
     100             : 
     101             :     /* If html_init was previously called on this output struct, just return. */
     102           0 :     if (out->priv != NULL) {
     103           0 :         return true;
     104             :     } else {
     105           0 :         out->priv = calloc(1, sizeof(private_data_t));
     106           0 :         if (out->priv == NULL) {
     107           0 :             return false;
     108             :         }
     109             : 
     110           0 :         priv = out->priv;
     111             :     }
     112             : 
     113           0 :     priv->parent_q = g_queue_new();
     114             : 
     115           0 :     priv->root = pcmk__xe_create(NULL, "html");
     116           0 :     xmlCreateIntSubset(priv->root->doc, (pcmkXmlStr) "html", NULL, NULL);
     117             : 
     118           0 :     crm_xml_add(priv->root, PCMK_XA_LANG, PCMK__VALUE_EN);
     119           0 :     g_queue_push_tail(priv->parent_q, priv->root);
     120           0 :     priv->errors = NULL;
     121             : 
     122           0 :     pcmk__output_xml_create_parent(out, "body", NULL);
     123             : 
     124           0 :     return true;
     125             : }
     126             : 
     127             : static void
     128           0 : add_error_node(gpointer data, gpointer user_data) {
     129           0 :     char *str = (char *) data;
     130           0 :     pcmk__output_t *out = (pcmk__output_t *) user_data;
     131           0 :     out->list_item(out, NULL, "%s", str);
     132           0 : }
     133             : 
     134             : static void
     135           0 : html_finish(pcmk__output_t *out, crm_exit_t exit_status, bool print, void **copy_dest) {
     136           0 :     private_data_t *priv = NULL;
     137           0 :     htmlNodePtr head_node = NULL;
     138           0 :     htmlNodePtr charset_node = NULL;
     139           0 :     xmlNode *child_node = NULL;
     140             : 
     141           0 :     CRM_ASSERT(out != NULL);
     142             : 
     143           0 :     priv = out->priv;
     144             : 
     145             :     /* If root is NULL, html_init failed and we are being called from pcmk__output_free
     146             :      * in the pcmk__output_new path.
     147             :      */
     148           0 :     if (priv == NULL || priv->root == NULL) {
     149           0 :         return;
     150             :     }
     151             : 
     152           0 :     if (cgi_output && print) {
     153           0 :         fprintf(out->dest, "Content-Type: text/html\n\n");
     154             :     }
     155             : 
     156             :     /* Add the head node last - it's not needed earlier because it doesn't contain
     157             :      * anything else that the user could add, and we want it done last to pick up
     158             :      * any options that may have been given.
     159             :      */
     160           0 :     head_node = xmlNewDocRawNode(NULL, NULL, (pcmkXmlStr) "head", NULL);
     161             : 
     162           0 :     if (title != NULL ) {
     163           0 :         child_node = pcmk__xe_create(head_node, "title");
     164           0 :         pcmk__xe_set_content(child_node, "%s", title);
     165           0 :     } else if (out->request != NULL) {
     166           0 :         child_node = pcmk__xe_create(head_node, "title");
     167           0 :         pcmk__xe_set_content(child_node, "%s", out->request);
     168             :     }
     169             : 
     170           0 :     charset_node = pcmk__xe_create(head_node, PCMK__XE_META);
     171           0 :     crm_xml_add(charset_node, "charset", "utf-8");
     172             : 
     173             :     /* Add any extra header nodes the caller might have created. */
     174           0 :     for (int i = 0; i < g_slist_length(extra_headers); i++) {
     175           0 :         xmlAddChild(head_node, xmlCopyNode(g_slist_nth_data(extra_headers, i), 1));
     176             :     }
     177             : 
     178             :     /* Stylesheets are included two different ways.  The first is via a built-in
     179             :      * default (see the stylesheet_default const above).  The second is via the
     180             :      * html-stylesheet option, and this should obviously be a link to a
     181             :      * stylesheet.  The second can override the first.  At least one should be
     182             :      * given.
     183             :      */
     184           0 :     child_node = pcmk__xe_create(head_node, "style");
     185           0 :     pcmk__xe_set_content(child_node, "%s", stylesheet_default);
     186             : 
     187           0 :     if (stylesheet_link != NULL) {
     188           0 :         htmlNodePtr link_node = pcmk__xe_create(head_node, "link");
     189           0 :         pcmk__xe_set_props(link_node, "rel", "stylesheet",
     190             :                            "href", stylesheet_link,
     191             :                            NULL);
     192             :     }
     193             : 
     194           0 :     xmlAddPrevSibling(priv->root->children, head_node);
     195             : 
     196           0 :     if (g_slist_length(priv->errors) > 0) {
     197           0 :         out->begin_list(out, "Errors", NULL, NULL);
     198           0 :         g_slist_foreach(priv->errors, add_error_node, (gpointer) out);
     199           0 :         out->end_list(out);
     200             :     }
     201             : 
     202           0 :     if (print) {
     203           0 :         htmlDocDump(out->dest, priv->root->doc);
     204             :     }
     205             : 
     206           0 :     if (copy_dest != NULL) {
     207           0 :         *copy_dest = pcmk__xml_copy(NULL, priv->root);
     208             :     }
     209             : 
     210           0 :     g_slist_free_full(extra_headers, (GDestroyNotify) xmlFreeNode);
     211           0 :     extra_headers = NULL;
     212             : }
     213             : 
     214             : static void
     215           0 : html_reset(pcmk__output_t *out) {
     216           0 :     CRM_ASSERT(out != NULL);
     217             : 
     218           0 :     out->dest = freopen(NULL, "w", out->dest);
     219           0 :     CRM_ASSERT(out->dest != NULL);
     220             : 
     221           0 :     html_free_priv(out);
     222           0 :     html_init(out);
     223           0 : }
     224             : 
     225             : static void
     226           0 : html_subprocess_output(pcmk__output_t *out, int exit_status,
     227             :                        const char *proc_stdout, const char *proc_stderr) {
     228           0 :     char *rc_buf = NULL;
     229             : 
     230           0 :     CRM_ASSERT(out != NULL);
     231             : 
     232           0 :     rc_buf = crm_strdup_printf("Return code: %d", exit_status);
     233             : 
     234           0 :     pcmk__output_create_xml_text_node(out, "h2", "Command Output");
     235           0 :     pcmk__output_create_html_node(out, PCMK__XE_DIV, NULL, NULL, rc_buf);
     236             : 
     237           0 :     if (proc_stdout != NULL) {
     238           0 :         pcmk__output_create_html_node(out, PCMK__XE_DIV, NULL, NULL, "Stdout");
     239           0 :         pcmk__output_create_html_node(out, PCMK__XE_DIV, NULL,
     240             :                                       PCMK__VALUE_OUTPUT, proc_stdout);
     241             :     }
     242           0 :     if (proc_stderr != NULL) {
     243           0 :         pcmk__output_create_html_node(out, PCMK__XE_DIV, NULL, NULL, "Stderr");
     244           0 :         pcmk__output_create_html_node(out, PCMK__XE_DIV, NULL,
     245             :                                       PCMK__VALUE_OUTPUT, proc_stderr);
     246             :     }
     247             : 
     248           0 :     free(rc_buf);
     249           0 : }
     250             : 
     251             : static void
     252           0 : html_version(pcmk__output_t *out, bool extended) {
     253           0 :     CRM_ASSERT(out != NULL);
     254             : 
     255           0 :     pcmk__output_create_xml_text_node(out, "h2", "Version Information");
     256           0 :     pcmk__output_create_html_node(out, PCMK__XE_DIV, NULL, NULL,
     257             :                                   "Program: Pacemaker");
     258           0 :     pcmk__output_create_html_node(out, PCMK__XE_DIV, NULL, NULL,
     259             :                                   "Version: " PACEMAKER_VERSION);
     260           0 :     pcmk__output_create_html_node(out, PCMK__XE_DIV, NULL, NULL,
     261             :                                   "Author: Andrew Beekhof and "
     262             :                                   "the Pacemaker project contributors");
     263           0 :     pcmk__output_create_html_node(out, PCMK__XE_DIV, NULL, NULL,
     264             :                                   "Build: " BUILD_VERSION);
     265           0 :     pcmk__output_create_html_node(out, PCMK__XE_DIV, NULL, NULL,
     266             :                                   "Features: " CRM_FEATURES);
     267           0 : }
     268             : 
     269             : G_GNUC_PRINTF(2, 3)
     270             : static void
     271           0 : html_err(pcmk__output_t *out, const char *format, ...) {
     272           0 :     private_data_t *priv = NULL;
     273           0 :     int len = 0;
     274           0 :     char *buf = NULL;
     275             :     va_list ap;
     276             : 
     277           0 :     CRM_ASSERT(out != NULL && out->priv != NULL);
     278           0 :     priv = out->priv;
     279             : 
     280           0 :     va_start(ap, format);
     281           0 :     len = vasprintf(&buf, format, ap);
     282           0 :     CRM_ASSERT(len >= 0);
     283           0 :     va_end(ap);
     284             : 
     285           0 :     priv->errors = g_slist_append(priv->errors, buf);
     286           0 : }
     287             : 
     288             : G_GNUC_PRINTF(2, 3)
     289             : static int
     290           0 : html_info(pcmk__output_t *out, const char *format, ...) {
     291           0 :     return pcmk_rc_no_output;
     292             : }
     293             : 
     294             : static void
     295           0 : html_output_xml(pcmk__output_t *out, const char *name, const char *buf) {
     296           0 :     htmlNodePtr node = NULL;
     297             : 
     298           0 :     CRM_ASSERT(out != NULL);
     299             : 
     300           0 :     node = pcmk__output_create_html_node(out, "pre", NULL, NULL, buf);
     301           0 :     crm_xml_add(node, PCMK_XA_LANG, "xml");
     302           0 : }
     303             : 
     304             : G_GNUC_PRINTF(4, 5)
     305             : static void
     306           0 : html_begin_list(pcmk__output_t *out, const char *singular_noun,
     307             :                 const char *plural_noun, const char *format, ...) {
     308           0 :     int q_len = 0;
     309           0 :     private_data_t *priv = NULL;
     310           0 :     xmlNodePtr node = NULL;
     311             : 
     312           0 :     CRM_ASSERT(out != NULL && out->priv != NULL);
     313           0 :     priv = out->priv;
     314             : 
     315             :     /* If we are already in a list (the queue depth is always at least
     316             :      * one because of the <html> element), first create a <li> element
     317             :      * to hold the <h2> and the new list.
     318             :      */
     319           0 :     q_len = g_queue_get_length(priv->parent_q);
     320           0 :     if (q_len > 2) {
     321           0 :         pcmk__output_xml_create_parent(out, "li", NULL);
     322             :     }
     323             : 
     324           0 :     if (format != NULL) {
     325             :         va_list ap;
     326           0 :         char *buf = NULL;
     327             :         int len;
     328             : 
     329           0 :         va_start(ap, format);
     330           0 :         len = vasprintf(&buf, format, ap);
     331           0 :         va_end(ap);
     332           0 :         CRM_ASSERT(len >= 0);
     333             : 
     334           0 :         if (q_len > 2) {
     335           0 :             pcmk__output_create_xml_text_node(out, "h3", buf);
     336             :         } else {
     337           0 :             pcmk__output_create_xml_text_node(out, "h2", buf);
     338             :         }
     339             : 
     340           0 :         free(buf);
     341             :     }
     342             : 
     343           0 :     node = pcmk__output_xml_create_parent(out, "ul", NULL);
     344           0 :     g_queue_push_tail(priv->parent_q, node);
     345           0 : }
     346             : 
     347             : G_GNUC_PRINTF(3, 4)
     348             : static void
     349           0 : html_list_item(pcmk__output_t *out, const char *name, const char *format, ...) {
     350           0 :     htmlNodePtr item_node = NULL;
     351             :     va_list ap;
     352           0 :     char *buf = NULL;
     353             :     int len;
     354             : 
     355           0 :     CRM_ASSERT(out != NULL);
     356             : 
     357           0 :     va_start(ap, format);
     358           0 :     len = vasprintf(&buf, format, ap);
     359           0 :     CRM_ASSERT(len >= 0);
     360           0 :     va_end(ap);
     361             : 
     362           0 :     item_node = pcmk__output_create_xml_text_node(out, "li", buf);
     363           0 :     free(buf);
     364             : 
     365           0 :     if (name != NULL) {
     366           0 :         crm_xml_add(item_node, PCMK_XA_CLASS, name);
     367             :     }
     368           0 : }
     369             : 
     370             : static void
     371           0 : html_increment_list(pcmk__output_t *out) {
     372             :     /* This function intentially left blank */
     373           0 : }
     374             : 
     375             : static void
     376           0 : html_end_list(pcmk__output_t *out) {
     377           0 :     private_data_t *priv = NULL;
     378             : 
     379           0 :     CRM_ASSERT(out != NULL && out->priv != NULL);
     380           0 :     priv = out->priv;
     381             : 
     382             :     /* Remove the <ul> tag, but do not free this result - it's still
     383             :      * part of the document.
     384             :      */
     385           0 :     g_queue_pop_tail(priv->parent_q);
     386           0 :     pcmk__output_xml_pop_parent(out);
     387             : 
     388             :     /* Remove the <li> created for nested lists. */
     389           0 :     if (g_queue_get_length(priv->parent_q) > 2) {
     390           0 :         pcmk__output_xml_pop_parent(out);
     391             :     }
     392           0 : }
     393             : 
     394             : static bool
     395           0 : html_is_quiet(pcmk__output_t *out) {
     396           0 :     return false;
     397             : }
     398             : 
     399             : static void
     400           0 : html_spacer(pcmk__output_t *out) {
     401           0 :     CRM_ASSERT(out != NULL);
     402           0 :     pcmk__output_create_xml_node(out, "br", NULL);
     403           0 : }
     404             : 
     405             : static void
     406           0 : html_progress(pcmk__output_t *out, bool end) {
     407             :     /* This function intentially left blank */
     408           0 : }
     409             : 
     410             : pcmk__output_t *
     411           0 : pcmk__mk_html_output(char **argv) {
     412           0 :     pcmk__output_t *retval = calloc(1, sizeof(pcmk__output_t));
     413             : 
     414           0 :     if (retval == NULL) {
     415           0 :         return NULL;
     416             :     }
     417             : 
     418           0 :     retval->fmt_name = "html";
     419           0 :     retval->request = pcmk__quote_cmdline(argv);
     420             : 
     421           0 :     retval->init = html_init;
     422           0 :     retval->free_priv = html_free_priv;
     423           0 :     retval->finish = html_finish;
     424           0 :     retval->reset = html_reset;
     425             : 
     426           0 :     retval->register_message = pcmk__register_message;
     427           0 :     retval->message = pcmk__call_message;
     428             : 
     429           0 :     retval->subprocess_output = html_subprocess_output;
     430           0 :     retval->version = html_version;
     431           0 :     retval->info = html_info;
     432           0 :     retval->transient = html_info;
     433           0 :     retval->err = html_err;
     434           0 :     retval->output_xml = html_output_xml;
     435             : 
     436           0 :     retval->begin_list = html_begin_list;
     437           0 :     retval->list_item = html_list_item;
     438           0 :     retval->increment_list = html_increment_list;
     439           0 :     retval->end_list = html_end_list;
     440             : 
     441           0 :     retval->is_quiet = html_is_quiet;
     442           0 :     retval->spacer = html_spacer;
     443           0 :     retval->progress = html_progress;
     444           0 :     retval->prompt = pcmk__text_prompt;
     445             : 
     446           0 :     return retval;
     447             : }
     448             : 
     449             : xmlNodePtr
     450           0 : pcmk__output_create_html_node(pcmk__output_t *out, const char *element_name, const char *id,
     451             :                               const char *class_name, const char *text) {
     452           0 :     htmlNodePtr node = NULL;
     453             : 
     454           0 :     CRM_ASSERT(out != NULL);
     455           0 :     CRM_CHECK(pcmk__str_eq(out->fmt_name, "html", pcmk__str_none), return NULL);
     456             : 
     457           0 :     node = pcmk__output_create_xml_text_node(out, element_name, text);
     458             : 
     459           0 :     if (class_name != NULL) {
     460           0 :         crm_xml_add(node, PCMK_XA_CLASS, class_name);
     461             :     }
     462             : 
     463           0 :     if (id != NULL) {
     464           0 :         crm_xml_add(node, PCMK_XA_ID, id);
     465             :     }
     466             : 
     467           0 :     return node;
     468             : }
     469             : 
     470             : /*!
     471             :  * \internal
     472             :  * \brief Create a new HTML element under a given parent with ID and class
     473             :  *
     474             :  * \param[in,out] parent  XML element that will be the new element's parent
     475             :  *                        (\c NULL to create a new XML document with the new
     476             :  *                        node as root)
     477             :  * \param[in]     name    Name of new element
     478             :  * \param[in]     id      CSS ID of new element (can be \c NULL)
     479             :  * \param[in]     class   CSS class of new element (can be \c NULL)
     480             :  *
     481             :  * \return Newly created XML element (guaranteed not to be \c NULL)
     482             :  */
     483             : xmlNode *
     484           0 : pcmk__html_create(xmlNode *parent, const char *name, const char *id,
     485             :                   const char *class)
     486             : {
     487           0 :     xmlNode *node = pcmk__xe_create(parent, name);
     488             : 
     489           0 :     pcmk__xe_set_props(node,
     490             :                        PCMK_XA_CLASS, class,
     491             :                        PCMK_XA_ID, id,
     492             :                        NULL);
     493           0 :     return node;
     494             : }
     495             : 
     496             : void
     497           0 : pcmk__html_add_header(const char *name, ...) {
     498             :     htmlNodePtr header_node;
     499             :     va_list ap;
     500             : 
     501           0 :     va_start(ap, name);
     502             : 
     503           0 :     header_node = xmlNewDocRawNode(NULL, NULL, (pcmkXmlStr) name, NULL);
     504           0 :     while (1) {
     505           0 :         char *key = va_arg(ap, char *);
     506             :         char *value;
     507             : 
     508           0 :         if (key == NULL) {
     509           0 :             break;
     510             :         }
     511             : 
     512           0 :         value = va_arg(ap, char *);
     513           0 :         crm_xml_add(header_node, key, value);
     514             :     }
     515             : 
     516           0 :     extra_headers = g_slist_append(extra_headers, header_node);
     517             : 
     518           0 :     va_end(ap);
     519           0 : }

Generated by: LCOV version 1.14