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

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