root/lib/common/output_html.c

/* [previous][next][first][last][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. html_free_priv
  2. html_init
  3. add_error_node
  4. html_finish
  5. html_reset
  6. html_subprocess_output
  7. html_version
  8. G_GNUC_PRINTF
  9. G_GNUC_PRINTF
  10. html_output_xml
  11. G_GNUC_PRINTF
  12. G_GNUC_PRINTF
  13. html_increment_list
  14. html_end_list
  15. html_is_quiet
  16. html_spacer
  17. html_progress
  18. pcmk__mk_html_output
  19. pcmk__output_create_html_node
  20. pcmk__html_create
  21. pcmk__html_add_header

   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 html_free_priv(pcmk__output_t *out) {
     /* [previous][next][first][last][top][bottom][index][help] */
  76     private_data_t *priv = NULL;
  77 
  78     if (out == NULL || out->priv == NULL) {
  79         return;
  80     }
  81 
  82     priv = out->priv;
  83 
  84     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     g_queue_free(priv->parent_q);
  90     g_slist_free_full(priv->errors, free);
  91     free(priv);
  92     out->priv = NULL;
  93 }
  94 
  95 static bool
  96 html_init(pcmk__output_t *out) {
     /* [previous][next][first][last][top][bottom][index][help] */
  97     private_data_t *priv = NULL;
  98 
  99     CRM_ASSERT(out != NULL);
 100 
 101     /* If html_init was previously called on this output struct, just return. */
 102     if (out->priv != NULL) {
 103         return true;
 104     } else {
 105         out->priv = calloc(1, sizeof(private_data_t));
 106         if (out->priv == NULL) {
 107             return false;
 108         }
 109 
 110         priv = out->priv;
 111     }
 112 
 113     priv->parent_q = g_queue_new();
 114 
 115     priv->root = pcmk__xe_create(NULL, "html");
 116     xmlCreateIntSubset(priv->root->doc, (pcmkXmlStr) "html", NULL, NULL);
 117 
 118     crm_xml_add(priv->root, PCMK_XA_LANG, PCMK__VALUE_EN);
 119     g_queue_push_tail(priv->parent_q, priv->root);
 120     priv->errors = NULL;
 121 
 122     pcmk__output_xml_create_parent(out, "body", NULL);
 123 
 124     return true;
 125 }
 126 
 127 static void
 128 add_error_node(gpointer data, gpointer user_data) {
     /* [previous][next][first][last][top][bottom][index][help] */
 129     char *str = (char *) data;
 130     pcmk__output_t *out = (pcmk__output_t *) user_data;
 131     out->list_item(out, NULL, "%s", str);
 132 }
 133 
 134 static void
 135 html_finish(pcmk__output_t *out, crm_exit_t exit_status, bool print, void **copy_dest) {
     /* [previous][next][first][last][top][bottom][index][help] */
 136     private_data_t *priv = NULL;
 137     htmlNodePtr head_node = NULL;
 138     htmlNodePtr charset_node = NULL;
 139     xmlNode *child_node = NULL;
 140 
 141     CRM_ASSERT(out != NULL);
 142 
 143     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     if (priv == NULL || priv->root == NULL) {
 149         return;
 150     }
 151 
 152     if (cgi_output && print) {
 153         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     head_node = xmlNewDocRawNode(NULL, NULL, (pcmkXmlStr) "head", NULL);
 161 
 162     if (title != NULL ) {
 163         child_node = pcmk__xe_create(head_node, "title");
 164         pcmk__xe_set_content(child_node, "%s", title);
 165     } else if (out->request != NULL) {
 166         child_node = pcmk__xe_create(head_node, "title");
 167         pcmk__xe_set_content(child_node, "%s", out->request);
 168     }
 169 
 170     charset_node = pcmk__xe_create(head_node, PCMK__XE_META);
 171     crm_xml_add(charset_node, "charset", "utf-8");
 172 
 173     /* Add any extra header nodes the caller might have created. */
 174     for (int i = 0; i < g_slist_length(extra_headers); i++) {
 175         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     child_node = pcmk__xe_create(head_node, "style");
 185     pcmk__xe_set_content(child_node, "%s", stylesheet_default);
 186 
 187     if (stylesheet_link != NULL) {
 188         htmlNodePtr link_node = pcmk__xe_create(head_node, "link");
 189         pcmk__xe_set_props(link_node, "rel", "stylesheet",
 190                            "href", stylesheet_link,
 191                            NULL);
 192     }
 193 
 194     xmlAddPrevSibling(priv->root->children, head_node);
 195 
 196     if (g_slist_length(priv->errors) > 0) {
 197         out->begin_list(out, "Errors", NULL, NULL);
 198         g_slist_foreach(priv->errors, add_error_node, (gpointer) out);
 199         out->end_list(out);
 200     }
 201 
 202     if (print) {
 203         htmlDocDump(out->dest, priv->root->doc);
 204     }
 205 
 206     if (copy_dest != NULL) {
 207         *copy_dest = pcmk__xml_copy(NULL, priv->root);
 208     }
 209 
 210     g_slist_free_full(extra_headers, (GDestroyNotify) xmlFreeNode);
 211     extra_headers = NULL;
 212 }
 213 
 214 static void
 215 html_reset(pcmk__output_t *out) {
     /* [previous][next][first][last][top][bottom][index][help] */
 216     CRM_ASSERT(out != NULL);
 217 
 218     out->dest = freopen(NULL, "w", out->dest);
 219     CRM_ASSERT(out->dest != NULL);
 220 
 221     html_free_priv(out);
 222     html_init(out);
 223 }
 224 
 225 static void
 226 html_subprocess_output(pcmk__output_t *out, int exit_status,
     /* [previous][next][first][last][top][bottom][index][help] */
 227                        const char *proc_stdout, const char *proc_stderr) {
 228     char *rc_buf = NULL;
 229 
 230     CRM_ASSERT(out != NULL);
 231 
 232     rc_buf = crm_strdup_printf("Return code: %d", exit_status);
 233 
 234     pcmk__output_create_xml_text_node(out, "h2", "Command Output");
 235     pcmk__output_create_html_node(out, PCMK__XE_DIV, NULL, NULL, rc_buf);
 236 
 237     if (proc_stdout != NULL) {
 238         pcmk__output_create_html_node(out, PCMK__XE_DIV, NULL, NULL, "Stdout");
 239         pcmk__output_create_html_node(out, PCMK__XE_DIV, NULL,
 240                                       PCMK__VALUE_OUTPUT, proc_stdout);
 241     }
 242     if (proc_stderr != NULL) {
 243         pcmk__output_create_html_node(out, PCMK__XE_DIV, NULL, NULL, "Stderr");
 244         pcmk__output_create_html_node(out, PCMK__XE_DIV, NULL,
 245                                       PCMK__VALUE_OUTPUT, proc_stderr);
 246     }
 247 
 248     free(rc_buf);
 249 }
 250 
 251 static void
 252 html_version(pcmk__output_t *out, bool extended) {
     /* [previous][next][first][last][top][bottom][index][help] */
 253     CRM_ASSERT(out != NULL);
 254 
 255     pcmk__output_create_xml_text_node(out, "h2", "Version Information");
 256     pcmk__output_create_html_node(out, PCMK__XE_DIV, NULL, NULL,
 257                                   "Program: Pacemaker");
 258     pcmk__output_create_html_node(out, PCMK__XE_DIV, NULL, NULL,
 259                                   "Version: " PACEMAKER_VERSION);
 260     pcmk__output_create_html_node(out, PCMK__XE_DIV, NULL, NULL,
 261                                   "Author: Andrew Beekhof and "
 262                                   "the Pacemaker project contributors");
 263     pcmk__output_create_html_node(out, PCMK__XE_DIV, NULL, NULL,
 264                                   "Build: " BUILD_VERSION);
 265     pcmk__output_create_html_node(out, PCMK__XE_DIV, NULL, NULL,
 266                                   "Features: " CRM_FEATURES);
 267 }
 268 
 269 G_GNUC_PRINTF(2, 3)
     /* [previous][next][first][last][top][bottom][index][help] */
 270 static void
 271 html_err(pcmk__output_t *out, const char *format, ...) {
 272     private_data_t *priv = NULL;
 273     int len = 0;
 274     char *buf = NULL;
 275     va_list ap;
 276 
 277     CRM_ASSERT(out != NULL && out->priv != NULL);
 278     priv = out->priv;
 279 
 280     va_start(ap, format);
 281     len = vasprintf(&buf, format, ap);
 282     CRM_ASSERT(len >= 0);
 283     va_end(ap);
 284 
 285     priv->errors = g_slist_append(priv->errors, buf);
 286 }
 287 
 288 G_GNUC_PRINTF(2, 3)
     /* [previous][next][first][last][top][bottom][index][help] */
 289 static int
 290 html_info(pcmk__output_t *out, const char *format, ...) {
 291     return pcmk_rc_no_output;
 292 }
 293 
 294 static void
 295 html_output_xml(pcmk__output_t *out, const char *name, const char *buf) {
     /* [previous][next][first][last][top][bottom][index][help] */
 296     htmlNodePtr node = NULL;
 297 
 298     CRM_ASSERT(out != NULL);
 299 
 300     node = pcmk__output_create_html_node(out, "pre", NULL, NULL, buf);
 301     crm_xml_add(node, PCMK_XA_LANG, "xml");
 302 }
 303 
 304 G_GNUC_PRINTF(4, 5)
     /* [previous][next][first][last][top][bottom][index][help] */
 305 static void
 306 html_begin_list(pcmk__output_t *out, const char *singular_noun,
 307                 const char *plural_noun, const char *format, ...) {
 308     int q_len = 0;
 309     private_data_t *priv = NULL;
 310     xmlNodePtr node = NULL;
 311 
 312     CRM_ASSERT(out != NULL && out->priv != NULL);
 313     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     q_len = g_queue_get_length(priv->parent_q);
 320     if (q_len > 2) {
 321         pcmk__output_xml_create_parent(out, "li", NULL);
 322     }
 323 
 324     if (format != NULL) {
 325         va_list ap;
 326         char *buf = NULL;
 327         int len;
 328 
 329         va_start(ap, format);
 330         len = vasprintf(&buf, format, ap);
 331         va_end(ap);
 332         CRM_ASSERT(len >= 0);
 333 
 334         if (q_len > 2) {
 335             pcmk__output_create_xml_text_node(out, "h3", buf);
 336         } else {
 337             pcmk__output_create_xml_text_node(out, "h2", buf);
 338         }
 339 
 340         free(buf);
 341     }
 342 
 343     node = pcmk__output_xml_create_parent(out, "ul", NULL);
 344     g_queue_push_tail(priv->parent_q, node);
 345 }
 346 
 347 G_GNUC_PRINTF(3, 4)
     /* [previous][next][first][last][top][bottom][index][help] */
 348 static void
 349 html_list_item(pcmk__output_t *out, const char *name, const char *format, ...) {
 350     htmlNodePtr item_node = NULL;
 351     va_list ap;
 352     char *buf = NULL;
 353     int len;
 354 
 355     CRM_ASSERT(out != NULL);
 356 
 357     va_start(ap, format);
 358     len = vasprintf(&buf, format, ap);
 359     CRM_ASSERT(len >= 0);
 360     va_end(ap);
 361 
 362     item_node = pcmk__output_create_xml_text_node(out, "li", buf);
 363     free(buf);
 364 
 365     if (name != NULL) {
 366         crm_xml_add(item_node, PCMK_XA_CLASS, name);
 367     }
 368 }
 369 
 370 static void
 371 html_increment_list(pcmk__output_t *out) {
     /* [previous][next][first][last][top][bottom][index][help] */
 372     /* This function intentially left blank */
 373 }
 374 
 375 static void
 376 html_end_list(pcmk__output_t *out) {
     /* [previous][next][first][last][top][bottom][index][help] */
 377     private_data_t *priv = NULL;
 378 
 379     CRM_ASSERT(out != NULL && out->priv != NULL);
 380     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     g_queue_pop_tail(priv->parent_q);
 386     pcmk__output_xml_pop_parent(out);
 387 
 388     /* Remove the <li> created for nested lists. */
 389     if (g_queue_get_length(priv->parent_q) > 2) {
 390         pcmk__output_xml_pop_parent(out);
 391     }
 392 }
 393 
 394 static bool
 395 html_is_quiet(pcmk__output_t *out) {
     /* [previous][next][first][last][top][bottom][index][help] */
 396     return false;
 397 }
 398 
 399 static void
 400 html_spacer(pcmk__output_t *out) {
     /* [previous][next][first][last][top][bottom][index][help] */
 401     CRM_ASSERT(out != NULL);
 402     pcmk__output_create_xml_node(out, "br", NULL);
 403 }
 404 
 405 static void
 406 html_progress(pcmk__output_t *out, bool end) {
     /* [previous][next][first][last][top][bottom][index][help] */
 407     /* This function intentially left blank */
 408 }
 409 
 410 pcmk__output_t *
 411 pcmk__mk_html_output(char **argv) {
     /* [previous][next][first][last][top][bottom][index][help] */
 412     pcmk__output_t *retval = calloc(1, sizeof(pcmk__output_t));
 413 
 414     if (retval == NULL) {
 415         return NULL;
 416     }
 417 
 418     retval->fmt_name = "html";
 419     retval->request = pcmk__quote_cmdline(argv);
 420 
 421     retval->init = html_init;
 422     retval->free_priv = html_free_priv;
 423     retval->finish = html_finish;
 424     retval->reset = html_reset;
 425 
 426     retval->register_message = pcmk__register_message;
 427     retval->message = pcmk__call_message;
 428 
 429     retval->subprocess_output = html_subprocess_output;
 430     retval->version = html_version;
 431     retval->info = html_info;
 432     retval->transient = html_info;
 433     retval->err = html_err;
 434     retval->output_xml = html_output_xml;
 435 
 436     retval->begin_list = html_begin_list;
 437     retval->list_item = html_list_item;
 438     retval->increment_list = html_increment_list;
 439     retval->end_list = html_end_list;
 440 
 441     retval->is_quiet = html_is_quiet;
 442     retval->spacer = html_spacer;
 443     retval->progress = html_progress;
 444     retval->prompt = pcmk__text_prompt;
 445 
 446     return retval;
 447 }
 448 
 449 xmlNodePtr
 450 pcmk__output_create_html_node(pcmk__output_t *out, const char *element_name, const char *id,
     /* [previous][next][first][last][top][bottom][index][help] */
 451                               const char *class_name, const char *text) {
 452     htmlNodePtr node = NULL;
 453 
 454     CRM_ASSERT(out != NULL);
 455     CRM_CHECK(pcmk__str_eq(out->fmt_name, "html", pcmk__str_none), return NULL);
 456 
 457     node = pcmk__output_create_xml_text_node(out, element_name, text);
 458 
 459     if (class_name != NULL) {
 460         crm_xml_add(node, PCMK_XA_CLASS, class_name);
 461     }
 462 
 463     if (id != NULL) {
 464         crm_xml_add(node, PCMK_XA_ID, id);
 465     }
 466 
 467     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 pcmk__html_create(xmlNode *parent, const char *name, const char *id,
     /* [previous][next][first][last][top][bottom][index][help] */
 485                   const char *class)
 486 {
 487     xmlNode *node = pcmk__xe_create(parent, name);
 488 
 489     pcmk__xe_set_props(node,
 490                        PCMK_XA_CLASS, class,
 491                        PCMK_XA_ID, id,
 492                        NULL);
 493     return node;
 494 }
 495 
 496 void
 497 pcmk__html_add_header(const char *name, ...) {
     /* [previous][next][first][last][top][bottom][index][help] */
 498     htmlNodePtr header_node;
 499     va_list ap;
 500 
 501     va_start(ap, name);
 502 
 503     header_node = xmlNewDocRawNode(NULL, NULL, (pcmkXmlStr) name, NULL);
 504     while (1) {
 505         char *key = va_arg(ap, char *);
 506         char *value;
 507 
 508         if (key == NULL) {
 509             break;
 510         }
 511 
 512         value = va_arg(ap, char *);
 513         crm_xml_add(header_node, key, value);
 514     }
 515 
 516     extra_headers = g_slist_append(extra_headers, header_node);
 517 
 518     va_end(ap);
 519 }

/* [previous][next][first][last][top][bottom][index][help] */